rowan-python 3.1.9__tar.gz → 3.1.10__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 (195) hide show
  1. {rowan_python-3.1.9 → rowan_python-3.1.10}/PKG-INFO +2 -2
  2. {rowan_python-3.1.9 → rowan_python-3.1.10}/docs/index.md +11 -0
  3. {rowan_python-3.1.9 → rowan_python-3.1.10}/examples/covalent_inhibitor_scan.py +3 -0
  4. {rowan_python-3.1.9 → rowan_python-3.1.10}/examples/docking.py +1 -1
  5. rowan_python-3.1.10/examples/logp.py +19 -0
  6. {rowan_python-3.1.9 → rowan_python-3.1.10}/examples/protein_md.py +1 -1
  7. {rowan_python-3.1.9 → rowan_python-3.1.10}/pixi.lock +16 -16
  8. {rowan_python-3.1.9 → rowan_python-3.1.10}/pyproject.toml +2 -2
  9. {rowan_python-3.1.9 → rowan_python-3.1.10}/rowan/__init__.py +1 -0
  10. {rowan_python-3.1.9 → rowan_python-3.1.10}/rowan/protein.py +62 -20
  11. {rowan_python-3.1.9 → rowan_python-3.1.10}/rowan/workflows/__init__.py +1 -0
  12. {rowan_python-3.1.9 → rowan_python-3.1.10}/rowan/workflows/analogue_docking.py +1 -1
  13. {rowan_python-3.1.9 → rowan_python-3.1.10}/rowan/workflows/base.py +2 -2
  14. {rowan_python-3.1.9 → rowan_python-3.1.10}/rowan/workflows/docking.py +1 -1
  15. rowan_python-3.1.10/rowan/workflows/logp.py +80 -0
  16. {rowan_python-3.1.9 → rowan_python-3.1.10}/rowan/workflows/pose_analysis_md.py +4 -2
  17. {rowan_python-3.1.9 → rowan_python-3.1.10}/rowan/workflows/protein_cofolding.py +6 -2
  18. {rowan_python-3.1.9 → rowan_python-3.1.10}/rowan/workflows/protein_md.py +30 -4
  19. {rowan_python-3.1.9 → rowan_python-3.1.10}/rowan/workflows/relative_binding_free_energy_perturbation.py +3 -1
  20. {rowan_python-3.1.9 → rowan_python-3.1.10}/skills/computational-chemistry-and-biology/SKILL.md +2 -1
  21. {rowan_python-3.1.9 → rowan_python-3.1.10}/skills/computational-chemistry-and-biology/reference/analogue_docking.md +1 -1
  22. {rowan_python-3.1.9 → rowan_python-3.1.10}/skills/computational-chemistry-and-biology/reference/batch_docking.md +2 -2
  23. {rowan_python-3.1.9 → rowan_python-3.1.10}/skills/computational-chemistry-and-biology/reference/covalent_inhibitor_scan.md +2 -0
  24. rowan_python-3.1.10/skills/computational-chemistry-and-biology/reference/logp.md +37 -0
  25. {rowan_python-3.1.9 → rowan_python-3.1.10}/skills/computational-chemistry-and-biology/reference/pocket_detection.md +2 -2
  26. {rowan_python-3.1.9 → rowan_python-3.1.10}/skills/computational-chemistry-and-biology/reference/protein_md.md +8 -4
  27. {rowan_python-3.1.9 → rowan_python-3.1.10}/skills/computational-chemistry-and-biology/reference/protein_prep.md +6 -2
  28. {rowan_python-3.1.9 → rowan_python-3.1.10}/skills/computational-chemistry-and-biology/reference/python_sdk.md +9 -9
  29. {rowan_python-3.1.9 → rowan_python-3.1.10}/skills/computational-chemistry-and-biology/reference/relative_binding_free_energy_perturbation.md +1 -1
  30. {rowan_python-3.1.9 → rowan_python-3.1.10}/.agents/plugins/marketplace.json +0 -0
  31. {rowan_python-3.1.9 → rowan_python-3.1.10}/.claude-plugin/marketplace.json +0 -0
  32. {rowan_python-3.1.9 → rowan_python-3.1.10}/.codex-plugin/plugin.json +0 -0
  33. {rowan_python-3.1.9 → rowan_python-3.1.10}/.envrc +0 -0
  34. {rowan_python-3.1.9 → rowan_python-3.1.10}/.github/workflows/build-and-deploy-docs.yml +0 -0
  35. {rowan_python-3.1.9 → rowan_python-3.1.10}/.github/workflows/publish-skill.yml +0 -0
  36. {rowan_python-3.1.9 → rowan_python-3.1.10}/.github/workflows/python-publish.yml +0 -0
  37. {rowan_python-3.1.9 → rowan_python-3.1.10}/.github/workflows/test.yml +0 -0
  38. {rowan_python-3.1.9 → rowan_python-3.1.10}/.gitignore +0 -0
  39. {rowan_python-3.1.9 → rowan_python-3.1.10}/.pre-commit-config.yaml +0 -0
  40. {rowan_python-3.1.9 → rowan_python-3.1.10}/AGENTS.md +0 -0
  41. {rowan_python-3.1.9 → rowan_python-3.1.10}/CLAUDE.md +0 -0
  42. {rowan_python-3.1.9 → rowan_python-3.1.10}/GEMINI.md +0 -0
  43. {rowan_python-3.1.9 → rowan_python-3.1.10}/LICENSE +0 -0
  44. {rowan_python-3.1.9 → rowan_python-3.1.10}/README.md +0 -0
  45. {rowan_python-3.1.9 → rowan_python-3.1.10}/docs/images/deciduous-tree-favicon.png +0 -0
  46. {rowan_python-3.1.9 → rowan_python-3.1.10}/docs/stylesheets/colors.css +0 -0
  47. {rowan_python-3.1.9 → rowan_python-3.1.10}/examples/PROTAC_solubility.py +0 -0
  48. {rowan_python-3.1.9 → rowan_python-3.1.10}/examples/admet.py +0 -0
  49. {rowan_python-3.1.9 → rowan_python-3.1.10}/examples/analogue_docking.py +0 -0
  50. {rowan_python-3.1.9 → rowan_python-3.1.10}/examples/aqueous_solubility.py +0 -0
  51. {rowan_python-3.1.9 → rowan_python-3.1.10}/examples/basic_calculation.py +0 -0
  52. {rowan_python-3.1.9 → rowan_python-3.1.10}/examples/basic_calculation_from_json.py +0 -0
  53. {rowan_python-3.1.9 → rowan_python-3.1.10}/examples/basic_calculation_with_constraint.py +0 -0
  54. {rowan_python-3.1.9 → rowan_python-3.1.10}/examples/basic_calculation_with_solvent.py +0 -0
  55. {rowan_python-3.1.9 → rowan_python-3.1.10}/examples/batch_docking.py +0 -0
  56. {rowan_python-3.1.9 → rowan_python-3.1.10}/examples/bde.py +0 -0
  57. {rowan_python-3.1.9 → rowan_python-3.1.10}/examples/binding_affinity.py +0 -0
  58. {rowan_python-3.1.9 → rowan_python-3.1.10}/examples/boltz_paired_msa.py +0 -0
  59. {rowan_python-3.1.9 → rowan_python-3.1.10}/examples/boltz_single_msa.py +0 -0
  60. {rowan_python-3.1.9 → rowan_python-3.1.10}/examples/chai_paired_msa.py +0 -0
  61. {rowan_python-3.1.9 → rowan_python-3.1.10}/examples/chai_single_msa.py +0 -0
  62. {rowan_python-3.1.9 → rowan_python-3.1.10}/examples/cofolding_screen.py +0 -0
  63. {rowan_python-3.1.9 → rowan_python-3.1.10}/examples/colabfold_paired_msa.py +0 -0
  64. {rowan_python-3.1.9 → rowan_python-3.1.10}/examples/colabfold_single_msa.py +0 -0
  65. {rowan_python-3.1.9 → rowan_python-3.1.10}/examples/conformer_dependent_redox.py +0 -0
  66. {rowan_python-3.1.9 → rowan_python-3.1.10}/examples/conformers.py +0 -0
  67. {rowan_python-3.1.9 → rowan_python-3.1.10}/examples/conformers_screen.py +0 -0
  68. {rowan_python-3.1.9 → rowan_python-3.1.10}/examples/covalent_docking.py +0 -0
  69. {rowan_python-3.1.9 → rowan_python-3.1.10}/examples/data/1iep_receptorH.pdb +0 -0
  70. {rowan_python-3.1.9 → rowan_python-3.1.10}/examples/data/Al_FCC.xyz +0 -0
  71. {rowan_python-3.1.9 → rowan_python-3.1.10}/examples/data/citalopram_1iep.xyz +0 -0
  72. {rowan_python-3.1.9 → rowan_python-3.1.10}/examples/data/ibuprofen_conformers.sdf +0 -0
  73. {rowan_python-3.1.9 → rowan_python-3.1.10}/examples/data/tyk2_ligands.sdf +0 -0
  74. {rowan_python-3.1.9 → rowan_python-3.1.10}/examples/data/tyk2_structure.pdb +0 -0
  75. {rowan_python-3.1.9 → rowan_python-3.1.10}/examples/data/workflow_example.json +0 -0
  76. {rowan_python-3.1.9 → rowan_python-3.1.10}/examples/dcd_download.py +0 -0
  77. {rowan_python-3.1.9 → rowan_python-3.1.10}/examples/descriptors.py +0 -0
  78. {rowan_python-3.1.9 → rowan_python-3.1.10}/examples/docking_screen.py +0 -0
  79. {rowan_python-3.1.9 → rowan_python-3.1.10}/examples/double_ended_ts_search.py +0 -0
  80. {rowan_python-3.1.9 → rowan_python-3.1.10}/examples/electronic_properties.py +0 -0
  81. {rowan_python-3.1.9 → rowan_python-3.1.10}/examples/estimate_workflow.py +0 -0
  82. {rowan_python-3.1.9 → rowan_python-3.1.10}/examples/fukui_index.py +0 -0
  83. {rowan_python-3.1.9 → rowan_python-3.1.10}/examples/hydrogen_bond_basicity.py +0 -0
  84. {rowan_python-3.1.9 → rowan_python-3.1.10}/examples/interaction_energy_decomposition.py +0 -0
  85. {rowan_python-3.1.9 → rowan_python-3.1.10}/examples/ion_mobility.py +0 -0
  86. {rowan_python-3.1.9 → rowan_python-3.1.10}/examples/irc.py +0 -0
  87. {rowan_python-3.1.9 → rowan_python-3.1.10}/examples/macropka.py +0 -0
  88. {rowan_python-3.1.9 → rowan_python-3.1.10}/examples/membrane_permeability.py +0 -0
  89. {rowan_python-3.1.9 → rowan_python-3.1.10}/examples/multistage_optimization.py +0 -0
  90. {rowan_python-3.1.9 → rowan_python-3.1.10}/examples/nmr.py +0 -0
  91. {rowan_python-3.1.9 → rowan_python-3.1.10}/examples/optimization.py +0 -0
  92. {rowan_python-3.1.9 → rowan_python-3.1.10}/examples/pdb_download.py +0 -0
  93. {rowan_python-3.1.9 → rowan_python-3.1.10}/examples/periodic_dft.py +0 -0
  94. {rowan_python-3.1.9 → rowan_python-3.1.10}/examples/phenol_pka.py +0 -0
  95. {rowan_python-3.1.9 → rowan_python-3.1.10}/examples/pka.py +0 -0
  96. {rowan_python-3.1.9 → rowan_python-3.1.10}/examples/pocket_detection.py +0 -0
  97. {rowan_python-3.1.9 → rowan_python-3.1.10}/examples/pose_analysis_md.py +0 -0
  98. {rowan_python-3.1.9 → rowan_python-3.1.10}/examples/project_scoped_api_key.py +0 -0
  99. {rowan_python-3.1.9 → rowan_python-3.1.10}/examples/protein_binder_design.py +0 -0
  100. {rowan_python-3.1.9 → rowan_python-3.1.10}/examples/protein_cofolding.py +0 -0
  101. {rowan_python-3.1.9 → rowan_python-3.1.10}/examples/protein_cofolding_with_constraints.py +0 -0
  102. {rowan_python-3.1.9 → rowan_python-3.1.10}/examples/protein_cofolding_with_templates.py +0 -0
  103. {rowan_python-3.1.9 → rowan_python-3.1.10}/examples/rbfe_graph.py +0 -0
  104. {rowan_python-3.1.9 → rowan_python-3.1.10}/examples/rbfe_resubmit.py +0 -0
  105. {rowan_python-3.1.9 → rowan_python-3.1.10}/examples/redox_potential.py +0 -0
  106. {rowan_python-3.1.9 → rowan_python-3.1.10}/examples/relative_binding_free_energy_perturbation.py +0 -0
  107. {rowan_python-3.1.9 → rowan_python-3.1.10}/examples/resubmit_with_perturbations.py +0 -0
  108. {rowan_python-3.1.9 → rowan_python-3.1.10}/examples/retrieve_workflow.py +0 -0
  109. {rowan_python-3.1.9 → rowan_python-3.1.10}/examples/scan.py +0 -0
  110. {rowan_python-3.1.9 → rowan_python-3.1.10}/examples/solvent_dependent_conformers.py +0 -0
  111. {rowan_python-3.1.9 → rowan_python-3.1.10}/examples/spin_states.py +0 -0
  112. {rowan_python-3.1.9 → rowan_python-3.1.10}/examples/strain.py +0 -0
  113. {rowan_python-3.1.9 → rowan_python-3.1.10}/examples/tautomer.py +0 -0
  114. {rowan_python-3.1.9 → rowan_python-3.1.10}/examples/template.py +0 -0
  115. {rowan_python-3.1.9 → rowan_python-3.1.10}/examples/webhook.py +0 -0
  116. {rowan_python-3.1.9 → rowan_python-3.1.10}/mkdocs.yml +0 -0
  117. {rowan_python-3.1.9 → rowan_python-3.1.10}/rowan/api_keys.py +0 -0
  118. {rowan_python-3.1.9 → rowan_python-3.1.10}/rowan/calculation.py +0 -0
  119. {rowan_python-3.1.9 → rowan_python-3.1.10}/rowan/config.py +0 -0
  120. {rowan_python-3.1.9 → rowan_python-3.1.10}/rowan/constants.py +0 -0
  121. {rowan_python-3.1.9 → rowan_python-3.1.10}/rowan/folder.py +0 -0
  122. {rowan_python-3.1.9 → rowan_python-3.1.10}/rowan/molecule.py +0 -0
  123. {rowan_python-3.1.9 → rowan_python-3.1.10}/rowan/project.py +0 -0
  124. {rowan_python-3.1.9 → rowan_python-3.1.10}/rowan/py.typed +0 -0
  125. {rowan_python-3.1.9 → rowan_python-3.1.10}/rowan/test_utils.py +0 -0
  126. {rowan_python-3.1.9 → rowan_python-3.1.10}/rowan/types.py +0 -0
  127. {rowan_python-3.1.9 → rowan_python-3.1.10}/rowan/user.py +0 -0
  128. {rowan_python-3.1.9 → rowan_python-3.1.10}/rowan/utils.py +0 -0
  129. {rowan_python-3.1.9 → rowan_python-3.1.10}/rowan/workflows/admet.py +0 -0
  130. {rowan_python-3.1.9 → rowan_python-3.1.10}/rowan/workflows/basic_calculation.py +0 -0
  131. {rowan_python-3.1.9 → rowan_python-3.1.10}/rowan/workflows/batch_docking.py +0 -0
  132. {rowan_python-3.1.9 → rowan_python-3.1.10}/rowan/workflows/bde.py +0 -0
  133. {rowan_python-3.1.9 → rowan_python-3.1.10}/rowan/workflows/binding_affinity.py +0 -0
  134. {rowan_python-3.1.9 → rowan_python-3.1.10}/rowan/workflows/conformer_search.py +0 -0
  135. {rowan_python-3.1.9 → rowan_python-3.1.10}/rowan/workflows/constants.py +0 -0
  136. {rowan_python-3.1.9 → rowan_python-3.1.10}/rowan/workflows/covalent_inhibitor_scan.py +0 -0
  137. {rowan_python-3.1.9 → rowan_python-3.1.10}/rowan/workflows/descriptors.py +0 -0
  138. {rowan_python-3.1.9 → rowan_python-3.1.10}/rowan/workflows/double_ended_ts_search.py +0 -0
  139. {rowan_python-3.1.9 → rowan_python-3.1.10}/rowan/workflows/electronic_properties.py +0 -0
  140. {rowan_python-3.1.9 → rowan_python-3.1.10}/rowan/workflows/fukui.py +0 -0
  141. {rowan_python-3.1.9 → rowan_python-3.1.10}/rowan/workflows/hydrogen_bond_donor_acceptor_strength.py +0 -0
  142. {rowan_python-3.1.9 → rowan_python-3.1.10}/rowan/workflows/interaction_energy_decomposition.py +0 -0
  143. {rowan_python-3.1.9 → rowan_python-3.1.10}/rowan/workflows/ion_mobility.py +0 -0
  144. {rowan_python-3.1.9 → rowan_python-3.1.10}/rowan/workflows/irc.py +0 -0
  145. {rowan_python-3.1.9 → rowan_python-3.1.10}/rowan/workflows/macropka.py +0 -0
  146. {rowan_python-3.1.9 → rowan_python-3.1.10}/rowan/workflows/membrane_permeability.py +0 -0
  147. {rowan_python-3.1.9 → rowan_python-3.1.10}/rowan/workflows/msa.py +0 -0
  148. {rowan_python-3.1.9 → rowan_python-3.1.10}/rowan/workflows/multistage_optimization.py +0 -0
  149. {rowan_python-3.1.9 → rowan_python-3.1.10}/rowan/workflows/nmr.py +0 -0
  150. {rowan_python-3.1.9 → rowan_python-3.1.10}/rowan/workflows/pka.py +0 -0
  151. {rowan_python-3.1.9 → rowan_python-3.1.10}/rowan/workflows/pocket_detection.py +0 -0
  152. {rowan_python-3.1.9 → rowan_python-3.1.10}/rowan/workflows/protein_binder_design.py +0 -0
  153. {rowan_python-3.1.9 → rowan_python-3.1.10}/rowan/workflows/rbfe_graph.py +0 -0
  154. {rowan_python-3.1.9 → rowan_python-3.1.10}/rowan/workflows/redox_potential.py +0 -0
  155. {rowan_python-3.1.9 → rowan_python-3.1.10}/rowan/workflows/scan.py +0 -0
  156. {rowan_python-3.1.9 → rowan_python-3.1.10}/rowan/workflows/solubility.py +0 -0
  157. {rowan_python-3.1.9 → rowan_python-3.1.10}/rowan/workflows/solvent_dependent_conformers.py +0 -0
  158. {rowan_python-3.1.9 → rowan_python-3.1.10}/rowan/workflows/spin_states.py +0 -0
  159. {rowan_python-3.1.9 → rowan_python-3.1.10}/rowan/workflows/strain.py +0 -0
  160. {rowan_python-3.1.9 → rowan_python-3.1.10}/rowan/workflows/tautomer_search.py +0 -0
  161. {rowan_python-3.1.9 → rowan_python-3.1.10}/skills/computational-chemistry-and-biology/reference/admet.md +0 -0
  162. {rowan_python-3.1.9 → rowan_python-3.1.10}/skills/computational-chemistry-and-biology/reference/basic_calculation.md +0 -0
  163. {rowan_python-3.1.9 → rowan_python-3.1.10}/skills/computational-chemistry-and-biology/reference/bde.md +0 -0
  164. {rowan_python-3.1.9 → rowan_python-3.1.10}/skills/computational-chemistry-and-biology/reference/binding_affinity.md +0 -0
  165. {rowan_python-3.1.9 → rowan_python-3.1.10}/skills/computational-chemistry-and-biology/reference/conformer_search.md +0 -0
  166. {rowan_python-3.1.9 → rowan_python-3.1.10}/skills/computational-chemistry-and-biology/reference/descriptors.md +0 -0
  167. {rowan_python-3.1.9 → rowan_python-3.1.10}/skills/computational-chemistry-and-biology/reference/docking.md +0 -0
  168. {rowan_python-3.1.9 → rowan_python-3.1.10}/skills/computational-chemistry-and-biology/reference/double_ended_ts_search.md +0 -0
  169. {rowan_python-3.1.9 → rowan_python-3.1.10}/skills/computational-chemistry-and-biology/reference/electronic_properties.md +0 -0
  170. {rowan_python-3.1.9 → rowan_python-3.1.10}/skills/computational-chemistry-and-biology/reference/folders_and_projects.md +0 -0
  171. {rowan_python-3.1.9 → rowan_python-3.1.10}/skills/computational-chemistry-and-biology/reference/fukui.md +0 -0
  172. {rowan_python-3.1.9 → rowan_python-3.1.10}/skills/computational-chemistry-and-biology/reference/hydrogen_bond_donor_acceptor_strength.md +0 -0
  173. {rowan_python-3.1.9 → rowan_python-3.1.10}/skills/computational-chemistry-and-biology/reference/interaction_energy_decomposition.md +0 -0
  174. {rowan_python-3.1.9 → rowan_python-3.1.10}/skills/computational-chemistry-and-biology/reference/ion_mobility.md +0 -0
  175. {rowan_python-3.1.9 → rowan_python-3.1.10}/skills/computational-chemistry-and-biology/reference/irc.md +0 -0
  176. {rowan_python-3.1.9 → rowan_python-3.1.10}/skills/computational-chemistry-and-biology/reference/macropka.md +0 -0
  177. {rowan_python-3.1.9 → rowan_python-3.1.10}/skills/computational-chemistry-and-biology/reference/mcp_execution.md +0 -0
  178. {rowan_python-3.1.9 → rowan_python-3.1.10}/skills/computational-chemistry-and-biology/reference/membrane_permeability.md +0 -0
  179. {rowan_python-3.1.9 → rowan_python-3.1.10}/skills/computational-chemistry-and-biology/reference/msa.md +0 -0
  180. {rowan_python-3.1.9 → rowan_python-3.1.10}/skills/computational-chemistry-and-biology/reference/multistage_optimization.md +0 -0
  181. {rowan_python-3.1.9 → rowan_python-3.1.10}/skills/computational-chemistry-and-biology/reference/nmr.md +0 -0
  182. {rowan_python-3.1.9 → rowan_python-3.1.10}/skills/computational-chemistry-and-biology/reference/pka.md +0 -0
  183. {rowan_python-3.1.9 → rowan_python-3.1.10}/skills/computational-chemistry-and-biology/reference/pose_analysis_md.md +0 -0
  184. {rowan_python-3.1.9 → rowan_python-3.1.10}/skills/computational-chemistry-and-biology/reference/protein_binder_design.md +0 -0
  185. {rowan_python-3.1.9 → rowan_python-3.1.10}/skills/computational-chemistry-and-biology/reference/protein_cofolding.md +0 -0
  186. {rowan_python-3.1.9 → rowan_python-3.1.10}/skills/computational-chemistry-and-biology/reference/rbfe_graph.md +0 -0
  187. {rowan_python-3.1.9 → rowan_python-3.1.10}/skills/computational-chemistry-and-biology/reference/redox_potential.md +0 -0
  188. {rowan_python-3.1.9 → rowan_python-3.1.10}/skills/computational-chemistry-and-biology/reference/scan.md +0 -0
  189. {rowan_python-3.1.9 → rowan_python-3.1.10}/skills/computational-chemistry-and-biology/reference/solubility.md +0 -0
  190. {rowan_python-3.1.9 → rowan_python-3.1.10}/skills/computational-chemistry-and-biology/reference/solvent_dependent_conformers.md +0 -0
  191. {rowan_python-3.1.9 → rowan_python-3.1.10}/skills/computational-chemistry-and-biology/reference/spin_states.md +0 -0
  192. {rowan_python-3.1.9 → rowan_python-3.1.10}/skills/computational-chemistry-and-biology/reference/strain.md +0 -0
  193. {rowan_python-3.1.9 → rowan_python-3.1.10}/skills/computational-chemistry-and-biology/reference/tautomer_search.md +0 -0
  194. {rowan_python-3.1.9 → rowan_python-3.1.10}/skills/computational-chemistry-and-biology/reference/webhooks.md +0 -0
  195. {rowan_python-3.1.9 → rowan_python-3.1.10}/skills/computational-chemistry-and-biology/scripts/check_env.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rowan-python
3
- Version: 3.1.9
3
+ Version: 3.1.10
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
@@ -11,7 +11,7 @@ Requires-Dist: httpx
11
11
  Requires-Dist: nest-asyncio
12
12
  Requires-Dist: rdkit
13
13
  Requires-Dist: setuptools
14
- Requires-Dist: stjames>=0.0.236
14
+ Requires-Dist: stjames<0.0.238,>=0.0.237
15
15
  Description-Content-Type: text/markdown
16
16
 
17
17
  # Rowan Python Library
@@ -209,6 +209,17 @@
209
209
  group_by_category: true
210
210
  filters: ["!^__"]
211
211
 
212
+ ## LogP
213
+ ::: rowan.workflows.logp
214
+ handler: python
215
+ options:
216
+ show_source: false
217
+ show_root_heading: false
218
+ show_root_toc_entry: false
219
+ members_order: source
220
+ group_by_category: true
221
+ filters: ["!^__"]
222
+
212
223
  ## MacropKa
213
224
  ::: rowan.workflows.macropka
214
225
  handler: python
@@ -15,10 +15,13 @@ protein.prepare(remove_heterogens=False)
15
15
  protein_reactive_atom_index = 1571
16
16
  ligand_reactive_atom_index = 4492
17
17
 
18
+ ligand_smiles = "CC(C)(C)C[C@@H](C#N)C(=O)N1CCC[C@H](C1)n2nc(c3ccc(Oc4ccccc4)cc3)c5c(N)ncnc25"
19
+
18
20
  workflow = rowan.submit_covalent_inhibitor_scan_workflow(
19
21
  protein=protein,
20
22
  protein_reactive_atom_index=protein_reactive_atom_index,
21
23
  ligand_reactive_atom_index=ligand_reactive_atom_index,
24
+ ligand_smiles=ligand_smiles,
22
25
  settings=rowan.CovalentInhibitorScanSettings(scan_num=4),
23
26
  name="BTK covalent inhibitor scan (Cys481, 4YHF)",
24
27
  folder=folder,
@@ -35,5 +35,5 @@ for i, score in enumerate(result.scores):
35
35
 
36
36
  # Download the top-scoring protein–ligand complex as a PDB
37
37
  complex_protein = result.get_complex(0)
38
- complex_protein.download_pdb_file("dasatinib_2GQG_complex.pdb")
38
+ complex_protein.download_pdb_file(name="dasatinib_2GQG_complex")
39
39
  print("Saved dasatinib_2GQG_complex.pdb")
@@ -0,0 +1,19 @@
1
+ import rowan
2
+
3
+ # Set your API key or use the ROWAN_API_KEY environment variable
4
+ # rowan.api_key = "rowan-sk..."
5
+ folder = rowan.get_folder("examples")
6
+
7
+ oseltamivir_SMILES = "C1CCOC(=O)C1=C[C@@H](OC(CC)CC)[C@H](NC(C)=O)[C@@H]([NH3+])C1CCC1"
8
+
9
+ workflow = rowan.submit_logp_workflow(
10
+ initial_smiles=oseltamivir_SMILES,
11
+ method="chemprop_sangster2026",
12
+ name="Oseltamivir logP",
13
+ folder=folder,
14
+ )
15
+
16
+ print(f"View workflow privately at: https://labs.rowansci.com/logp/{workflow.uuid}")
17
+ result = workflow.result()
18
+ print(result)
19
+ # e.g. <LogPResult logp=2.541>
@@ -5,7 +5,7 @@ import rowan
5
5
  folder = rowan.get_folder("examples")
6
6
 
7
7
  protein = rowan.create_protein_from_pdb_id(
8
- "crambin", "1CRN", project_uuid=rowan.default_project().uuid
8
+ "1CRN", name="crambin", project_uuid=rowan.default_project().uuid
9
9
  )
10
10
 
11
11
  protein.prepare()
@@ -55,9 +55,9 @@ environments:
55
55
  - pypi: https://files.pythonhosted.org/packages/89/1d/8eff589b45bb8190a9d12c49cfad0f176a5cbd1534908a6b5125e2886239/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
56
56
  - pypi: https://files.pythonhosted.org/packages/a0/c4/c2971a3ba4c6103a3d10c4b0f24f461ddc027f0f09763220cf35ca1401b3/nest_asyncio-1.6.0-py3-none-any.whl
57
57
  - pypi: https://files.pythonhosted.org/packages/a0/f4/c67b0b3f1b9245e8d266f0f112c500d50e5b4e83cb6f3b71b6528104182a/requests-2.34.2-py3-none-any.whl
58
- - pypi: https://files.pythonhosted.org/packages/d0/fb/be7d2e16e6c6143cd6834cd488d990d75352a5c9cdad6d5c615b5f01d736/stjames-0.0.236-py3-none-any.whl
59
58
  - pypi: https://files.pythonhosted.org/packages/da/35/f2287558c17e29fafc8ef3daf819bb9834061cfa43bff8014f7df7f63bdc/anyio-4.14.2-py3-none-any.whl
60
59
  - pypi: https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl
60
+ - pypi: https://files.pythonhosted.org/packages/df/95/bdd01bbd523a63b113407c34c99af8495daa4651757cc28f3fabc393d131/stjames-0.0.237-py3-none-any.whl
61
61
  - pypi: https://files.pythonhosted.org/packages/e1/11/1a9a693e424b2a75755d56ecb67ccc6f77e6e5bb7417dec5e73d9c410314/rdkit-2026.3.4-cp314-cp314-manylinux_2_28_x86_64.whl
62
62
  - pypi: https://files.pythonhosted.org/packages/e8/3d/1087453384dbde46a8c7f9356eead2c58be8a7bf156bca40243377c85715/more_itertools-11.1.0-py3-none-any.whl
63
63
  - pypi: https://files.pythonhosted.org/packages/f4/c4/b3e049d2aa3766180c78507110543d9d50894cc97f57de543f1be521dcdc/charset_normalizer-3.4.9-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl
@@ -96,9 +96,9 @@ environments:
96
96
  - pypi: https://files.pythonhosted.org/packages/ad/1f/8970b150a4b4365623ae00fc88603491f763c627311ae8031e3111356d6e/pydantic_core-2.46.4-cp314-cp314-macosx_11_0_arm64.whl
97
97
  - pypi: https://files.pythonhosted.org/packages/bf/64/0e215f2048dd11a55bb989ed41b3585ef57452404e638d703a211a3e4157/numpy-2.5.1-cp314-cp314-macosx_11_0_arm64.whl
98
98
  - pypi: https://files.pythonhosted.org/packages/c7/da/32c752228ae345f489e3a42499d817b6c3996da7e8a3bc7a04fc806b243b/pillow-12.3.0-cp314-cp314-macosx_11_0_arm64.whl
99
- - pypi: https://files.pythonhosted.org/packages/d0/fb/be7d2e16e6c6143cd6834cd488d990d75352a5c9cdad6d5c615b5f01d736/stjames-0.0.236-py3-none-any.whl
100
99
  - pypi: https://files.pythonhosted.org/packages/da/35/f2287558c17e29fafc8ef3daf819bb9834061cfa43bff8014f7df7f63bdc/anyio-4.14.2-py3-none-any.whl
101
100
  - pypi: https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl
101
+ - pypi: https://files.pythonhosted.org/packages/df/95/bdd01bbd523a63b113407c34c99af8495daa4651757cc28f3fabc393d131/stjames-0.0.237-py3-none-any.whl
102
102
  - pypi: https://files.pythonhosted.org/packages/e8/3d/1087453384dbde46a8c7f9356eead2c58be8a7bf156bca40243377c85715/more_itertools-11.1.0-py3-none-any.whl
103
103
  - pypi: https://files.pythonhosted.org/packages/fd/7b/122376b1fd3c62c1ed9dc80c931ace4844b3c55407b6fb2d199377c9736f/pydantic-2.13.4-py3-none-any.whl
104
104
  dev:
@@ -192,7 +192,6 @@ environments:
192
192
  - pypi: https://files.pythonhosted.org/packages/c7/7b/14882602ddee241d7984a742fcb423cb4a30fb0d6efc546ac3129fba475a/python_discovery-1.5.0-py3-none-any.whl
193
193
  - pypi: https://files.pythonhosted.org/packages/ca/87/b4d6c38e0ccd5e85dc54cecdf933a152c60b28fe5d993a6d8a72fa6d5896/ast_serialize-0.6.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
194
194
  - pypi: https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl
195
- - pypi: https://files.pythonhosted.org/packages/d0/fb/be7d2e16e6c6143cd6834cd488d990d75352a5c9cdad6d5c615b5f01d736/stjames-0.0.236-py3-none-any.whl
196
195
  - pypi: https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl
197
196
  - pypi: https://files.pythonhosted.org/packages/d1/fc/10ab7e80650a9c9e8f4f1105f8c8e73567f88ed0c06ada589ab81d38687c/mkdocstrings_python-2.0.5-py3-none-any.whl
198
197
  - pypi: https://files.pythonhosted.org/packages/d4/2b/04b8a15f3a1c77bc79ddf5c73875327f34b4fa75982df2b76e45e402d364/asttokens-3.0.2-py3-none-any.whl
@@ -202,6 +201,7 @@ environments:
202
201
  - pypi: https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl
203
202
  - pypi: https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl
204
203
  - pypi: https://files.pythonhosted.org/packages/de/1f/77fa3081e4f66ca3576c896ae5d31c3002ac6607f9747d2e3aa49227e464/markdown-3.10.2-py3-none-any.whl
204
+ - pypi: https://files.pythonhosted.org/packages/df/95/bdd01bbd523a63b113407c34c99af8495daa4651757cc28f3fabc393d131/stjames-0.0.237-py3-none-any.whl
205
205
  - pypi: https://files.pythonhosted.org/packages/df/b2/87e62e8c3e2f4b32e5fe99e0b86d576da1312593b39f47d8ceef365e95ed/packaging-26.2-py3-none-any.whl
206
206
  - pypi: https://files.pythonhosted.org/packages/e1/11/1a9a693e424b2a75755d56ecb67ccc6f77e6e5bb7417dec5e73d9c410314/rdkit-2026.3.4-cp314-cp314-manylinux_2_28_x86_64.whl
207
207
  - pypi: https://files.pythonhosted.org/packages/e4/d3/5268aeabf2ad82658c4e2ff3a060648d0f02f3926cb53247c0e4d0dab49e/griffelib-2.1.0-py3-none-any.whl
@@ -296,7 +296,6 @@ environments:
296
296
  - pypi: https://files.pythonhosted.org/packages/c7/7b/14882602ddee241d7984a742fcb423cb4a30fb0d6efc546ac3129fba475a/python_discovery-1.5.0-py3-none-any.whl
297
297
  - pypi: https://files.pythonhosted.org/packages/c7/da/32c752228ae345f489e3a42499d817b6c3996da7e8a3bc7a04fc806b243b/pillow-12.3.0-cp314-cp314-macosx_11_0_arm64.whl
298
298
  - pypi: https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl
299
- - pypi: https://files.pythonhosted.org/packages/d0/fb/be7d2e16e6c6143cd6834cd488d990d75352a5c9cdad6d5c615b5f01d736/stjames-0.0.236-py3-none-any.whl
300
299
  - pypi: https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl
301
300
  - pypi: https://files.pythonhosted.org/packages/d1/fc/10ab7e80650a9c9e8f4f1105f8c8e73567f88ed0c06ada589ab81d38687c/mkdocstrings_python-2.0.5-py3-none-any.whl
302
301
  - pypi: https://files.pythonhosted.org/packages/d4/2b/04b8a15f3a1c77bc79ddf5c73875327f34b4fa75982df2b76e45e402d364/asttokens-3.0.2-py3-none-any.whl
@@ -307,6 +306,7 @@ environments:
307
306
  - pypi: https://files.pythonhosted.org/packages/db/7d/7f3d619e951c88ed75c6037b246ddcf2d322812ee8ea189be89511721d54/watchdog-6.0.0.tar.gz
308
307
  - pypi: https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl
309
308
  - pypi: https://files.pythonhosted.org/packages/de/1f/77fa3081e4f66ca3576c896ae5d31c3002ac6607f9747d2e3aa49227e464/markdown-3.10.2-py3-none-any.whl
309
+ - pypi: https://files.pythonhosted.org/packages/df/95/bdd01bbd523a63b113407c34c99af8495daa4651757cc28f3fabc393d131/stjames-0.0.237-py3-none-any.whl
310
310
  - pypi: https://files.pythonhosted.org/packages/df/b2/87e62e8c3e2f4b32e5fe99e0b86d576da1312593b39f47d8ceef365e95ed/packaging-26.2-py3-none-any.whl
311
311
  - pypi: https://files.pythonhosted.org/packages/e4/d3/5268aeabf2ad82658c4e2ff3a060648d0f02f3926cb53247c0e4d0dab49e/griffelib-2.1.0-py3-none-any.whl
312
312
  - pypi: https://files.pythonhosted.org/packages/e8/3d/1087453384dbde46a8c7f9356eead2c58be8a7bf156bca40243377c85715/more_itertools-11.1.0-py3-none-any.whl
@@ -820,7 +820,7 @@ packages:
820
820
  name: rowan-python
821
821
  requires_dist:
822
822
  - httpx
823
- - stjames>=0.0.236
823
+ - stjames>=0.0.237,<0.0.238
824
824
  - setuptools
825
825
  - rdkit
826
826
  - nest-asyncio
@@ -1706,17 +1706,6 @@ packages:
1706
1706
  version: 2.3.0
1707
1707
  sha256: f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12
1708
1708
  requires_python: '>=3.10'
1709
- - pypi: https://files.pythonhosted.org/packages/d0/fb/be7d2e16e6c6143cd6834cd488d990d75352a5c9cdad6d5c615b5f01d736/stjames-0.0.236-py3-none-any.whl
1710
- name: stjames
1711
- version: 0.0.236
1712
- sha256: ea1459a5d0e0ccd206e5a387b201beec5e60ad6bc4f15dbcf649e2d4667fc40c
1713
- requires_dist:
1714
- - pydantic>=2.4
1715
- - numpy
1716
- - requests
1717
- - more-itertools
1718
- - rdkit ; extra == 'rdkit'
1719
- requires_python: '>=3.11'
1720
1709
  - pypi: https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl
1721
1710
  name: colorama
1722
1711
  version: 0.4.6
@@ -1803,6 +1792,17 @@ packages:
1803
1792
  - mkdocs-section-index ; extra == 'docs'
1804
1793
  - mkdocs-literate-nav ; extra == 'docs'
1805
1794
  requires_python: '>=3.10'
1795
+ - pypi: https://files.pythonhosted.org/packages/df/95/bdd01bbd523a63b113407c34c99af8495daa4651757cc28f3fabc393d131/stjames-0.0.237-py3-none-any.whl
1796
+ name: stjames
1797
+ version: 0.0.237
1798
+ sha256: 790c32bdc829efe52b7ccc442083d0d95fc01587d4063a08ea73cd9f686f6d21
1799
+ requires_dist:
1800
+ - pydantic>=2.4
1801
+ - numpy
1802
+ - requests
1803
+ - more-itertools
1804
+ - rdkit ; extra == 'rdkit'
1805
+ requires_python: '>=3.11'
1806
1806
  - pypi: https://files.pythonhosted.org/packages/df/b2/87e62e8c3e2f4b32e5fe99e0b86d576da1312593b39f47d8ceef365e95ed/packaging-26.2-py3-none-any.whl
1807
1807
  name: packaging
1808
1808
  version: '26.2'
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "rowan-python"
3
- version = "3.1.9"
3
+ version = "3.1.10"
4
4
  description = "Rowan Python Library"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.12"
@@ -9,7 +9,7 @@ authors = [
9
9
  ]
10
10
  dependencies = [
11
11
  "httpx",
12
- "stjames>=0.0.236",
12
+ "stjames>=0.0.237,<0.0.238",
13
13
  "setuptools",
14
14
  "rdkit",
15
15
  "nest-asyncio",
@@ -3,6 +3,7 @@ from . import constants
3
3
  from stjames import (
4
4
  Atom,
5
5
  BandStructure,
6
+ Binder,
6
7
  BindingPoseContact,
7
8
  HydrationBridgeResidue,
8
9
  HydrationSite,
@@ -4,7 +4,8 @@ from datetime import datetime
4
4
  from pathlib import Path
5
5
  from typing import Any, Self
6
6
 
7
- from pydantic import BaseModel
7
+ from pydantic import BaseModel, PrivateAttr
8
+ from stjames.pdb import PDB, pdb_object_to_pdb_filestring
8
9
 
9
10
  from .project import Project
10
11
  from .utils import api_client
@@ -37,6 +38,8 @@ class Protein(BaseModel):
37
38
  public: bool | None = None
38
39
  pocket: list[list[float]] | None = None
39
40
 
41
+ _workflow_uuid: str | None = PrivateAttr(default=None)
42
+
40
43
  def __repr__(self) -> str:
41
44
  return f"<Protein name='{self.name}' created_at='{self.created_at}' uuid='{self.uuid}'>"
42
45
 
@@ -119,18 +122,24 @@ class Protein(BaseModel):
119
122
  response.raise_for_status()
120
123
  return Protein(**response.json())
121
124
 
122
- def refresh(self, in_place: bool = True) -> Self:
125
+ def refresh(self, in_place: bool = True, workflow_uuid: str | None = None) -> Self:
123
126
  """
124
127
  Loads protein data
125
128
 
129
+ :param workflow_uuid: UUID of a workflow referencing this protein, for proteins reachable
130
+ only through a workflow you can read. Not needed for proteins from a workflow result.
126
131
  :returns: protein with loaded data
127
132
  """
133
+ workflow_uuid = workflow_uuid or self._workflow_uuid
134
+ params = {"workflow_uuid": workflow_uuid} if workflow_uuid else None
128
135
  with api_client() as client:
129
- response = client.get(f"/protein/{self.uuid}")
136
+ response = client.get(f"/protein/{self.uuid}", params=params)
130
137
  response.raise_for_status()
131
138
  protein_data = response.json()
132
139
  if not in_place:
133
- return self.__class__.model_validate(protein_data)
140
+ refreshed = self.__class__.model_validate(protein_data)
141
+ refreshed._workflow_uuid = workflow_uuid
142
+ return refreshed
134
143
 
135
144
  self.name = protein_data.get("name")
136
145
  self.data = protein_data.get("data")
@@ -138,6 +147,7 @@ class Protein(BaseModel):
138
147
  self.pocket = protein_data.get("pocket")
139
148
  self.sanitized = protein_data.get("sanitized")
140
149
  self.used_in_workflow = protein_data.get("used_in_workflow")
150
+ self._workflow_uuid = workflow_uuid
141
151
  return self
142
152
 
143
153
  def update(
@@ -291,7 +301,7 @@ class Protein(BaseModel):
291
301
 
292
302
  raise RuntimeError(f"Protein preparation timed out after {timeout:.0f}s for {self.uuid}.")
293
303
 
294
- def validate_protein_forcefield(self, exclude_residue_names: list[str] | None = None) -> None:
304
+ def validate_protein_forcefield(self, exclude_residues: list[str | int] | None = None) -> None:
295
305
  """
296
306
  Validate that this protein can be parameterized with the MD forcefield.
297
307
 
@@ -299,16 +309,24 @@ class Protein(BaseModel):
299
309
  recognized by OpenMM and that there are no clashing atoms. Call this
300
310
  before submitting any MD workflow to catch preparation issues early.
301
311
 
302
- Ligand residues (``LIG``) are always excluded — they are parameterized
303
- separately by the MD workflow from the provided SMILES.
312
+ Ligand residues (`LIG`) are always excluded — they are parameterized
313
+ separately by the MD workflow from the provided SMILES. Pass the keys of a
314
+ binder's `small_molecules` to exclude those as well.
315
+
316
+ A name is matched case-insensitively and excludes only the first residue
317
+ with that name, so further copies are still validated. Integer entries are
318
+ 0-based indices into the protein's sorted non-polymer records, and reference
319
+ a record without naming it.
304
320
 
305
321
  If validation fails, try re-preparing with ``remove_invalid_hydrogens=True``:
306
322
  ``protein.prepare(remove_invalid_hydrogens=True)``
307
323
 
308
- :param exclude_residue_names: Additional residue names to skip during validation.
324
+ :param exclude_residues: additional residue names and/or 0-based non-polymer indices to skip
309
325
  :raises requests.HTTPError: if validation fails or the API request fails.
310
326
  """
311
- excluded = list({"LIG"} | {name.upper() for name in (exclude_residue_names or [])})
327
+ entries: list[str | int] = ["LIG"]
328
+ entries += [r.upper() if isinstance(r, str) else r for r in exclude_residues or []]
329
+ excluded = list(dict.fromkeys(entries))
312
330
  with api_client() as client:
313
331
  response = client.post(
314
332
  f"/protein/{self.uuid}/validate_forcefield",
@@ -316,42 +334,66 @@ class Protein(BaseModel):
316
334
  )
317
335
  response.raise_for_status()
318
336
 
319
- def download_pdb_file(self, path: Path | str | None = None, name: str | None = None) -> None:
337
+ def download_pdb_file(
338
+ self,
339
+ path: Path | str | None = None,
340
+ name: str | None = None,
341
+ workflow_uuid: str | None = None,
342
+ ) -> None:
320
343
  """
321
- Downloads the PDB file for a protein
344
+ Downloads the PDB file for a protein.
345
+
346
+ Builds the file locally from this protein's data, calling `refresh()` if needed, so it
347
+ works anywhere `refresh()` does — including proteins reachable only through a workflow.
322
348
 
323
349
  :param path: Directory to save the file to (defaults to current directory)
324
350
  :param name: Optional custom name for the file (defaults to protein name)
351
+ :param workflow_uuid: UUID of a workflow referencing this protein, for proteins reachable
352
+ only through a workflow you can read. Not needed for proteins from a workflow result.
325
353
  :raises requests.HTTPError: if the request to the API fails
326
354
  """
327
355
  path = Path(path) if path is not None else Path.cwd()
328
356
 
329
357
  path.mkdir(parents=True, exist_ok=True)
330
358
 
331
- with api_client() as client:
332
- response = client.get(f"/protein/{self.uuid}/get_pdb_file")
333
- response.raise_for_status()
359
+ if self.data is None:
360
+ self.refresh(workflow_uuid=workflow_uuid)
361
+
362
+ pdb_object = PDB.model_validate(self.data)
363
+ pdb_string = pdb_object_to_pdb_filestring(
364
+ pdb=pdb_object,
365
+ header=True,
366
+ source=True,
367
+ keyword=True,
368
+ crystallography=True,
369
+ remark=False,
370
+ )
334
371
 
335
- file_path = path / f"{name or self.name}.pdb"
336
- with open(file_path, "wb") as f:
337
- f.write(response.content)
372
+ file_path = path / f"{name or self.name or self.uuid}.pdb"
373
+ with open(file_path, "w") as f:
374
+ f.write(pdb_string)
338
375
 
339
376
 
340
- def retrieve_protein(uuid: str) -> Protein:
377
+ def retrieve_protein(uuid: str, workflow_uuid: str | None = None) -> Protein:
341
378
  """
342
379
  Retrieves a protein from the API using its UUID.
343
380
 
344
381
  :param uuid: UUID of the protein to retrieve.
382
+ :param workflow_uuid: UUID of a workflow referencing this protein, for proteins reachable
383
+ only through a workflow you can read. Not needed for proteins from a workflow result.
345
384
  :returns: Protein object representing the retrieved protein.
346
385
  :raises requests.HTTPError: if the request to the API fails.
347
386
  """
387
+ params = {"workflow_uuid": workflow_uuid} if workflow_uuid else None
348
388
 
349
389
  with api_client() as client:
350
- response = client.get(f"/protein/{uuid}")
390
+ response = client.get(f"/protein/{uuid}", params=params)
351
391
  response.raise_for_status()
352
392
  protein_data = response.json()
353
393
 
354
- return Protein(**protein_data)
394
+ protein = Protein(**protein_data)
395
+ protein._workflow_uuid = workflow_uuid
396
+ return protein
355
397
 
356
398
 
357
399
  def list_proteins(
@@ -83,6 +83,7 @@ from .interaction_energy_decomposition import (
83
83
  )
84
84
  from .ion_mobility import IonMobilityResult, submit_ion_mobility_workflow
85
85
  from .irc import IRCResult, submit_irc_workflow
86
+ from .logp import LogPResult, submit_logp_workflow
86
87
  from .macropka import (
87
88
  MacropKaMicrostate,
88
89
  MacropKaResult,
@@ -160,7 +160,7 @@ class AnalogueDockingResult(WorkflowResult):
160
160
 
161
161
  cache_key = f"complex_{smiles}_{index}"
162
162
  if cache_key not in self._cache:
163
- self._cache[cache_key] = retrieve_protein(uuid)
163
+ self._cache[cache_key] = retrieve_protein(uuid, workflow_uuid=self.workflow_uuid)
164
164
  return self._cache[cache_key]
165
165
 
166
166
  def get_complexes(self, smiles: str) -> list[Protein]:
@@ -144,10 +144,10 @@ the server (a field is missing, extra, or the wrong type). Underlying validation
144
144
  RESULT_REGISTRY: dict[str, type[WorkflowResult]] = {}
145
145
 
146
146
 
147
- def register_result(workflow_type: str) -> Callable[[type[WorkflowResult]], type[WorkflowResult]]:
147
+ def register_result[R: WorkflowResult](workflow_type: str) -> Callable[[type[R]], type[R]]:
148
148
  """Decorator to register a result class for a workflow type."""
149
149
 
150
- def decorator(cls: type[WorkflowResult]) -> type[WorkflowResult]:
150
+ def decorator(cls: type[R]) -> type[R]:
151
151
  RESULT_REGISTRY[workflow_type] = cls
152
152
  return cls
153
153
 
@@ -133,7 +133,7 @@ class DockingResult(WorkflowResult):
133
133
 
134
134
  cache_key = f"complex_{index}"
135
135
  if cache_key not in self._cache:
136
- self._cache[cache_key] = retrieve_protein(uuid)
136
+ self._cache[cache_key] = retrieve_protein(uuid, workflow_uuid=self.workflow_uuid)
137
137
  return self._cache[cache_key]
138
138
 
139
139
  def get_complexes(self) -> list[Protein]:
@@ -0,0 +1,80 @@
1
+ """logP workflow - predict the octanol/water partition coefficient."""
2
+
3
+ from typing import Literal
4
+
5
+ import stjames
6
+
7
+ from ..folder import Folder
8
+ from ..utils import api_client
9
+ from .base import SMILES, Workflow, WorkflowResult, extract_smiles, register_result
10
+
11
+
12
+ @register_result("logp")
13
+ class LogPResult(WorkflowResult):
14
+ """Result from a logP workflow."""
15
+
16
+ _stjames_class = stjames.LogPWorkflow
17
+
18
+ def __repr__(self) -> str:
19
+ return f"<LogPResult logp={self.logp}>"
20
+
21
+ @property
22
+ def logp(self) -> float | None:
23
+ """Predicted octanol/water partition coefficient."""
24
+ return self._workflow.logp
25
+
26
+
27
+ def submit_logp_workflow(
28
+ initial_smiles: SMILES,
29
+ method: Literal["chemprop_sangster2026", "crippen", "cosmors"] = "chemprop_sangster2026",
30
+ name: str = "LogP Workflow",
31
+ folder_uuid: str | None = None,
32
+ folder: Folder | None = None,
33
+ max_credits: int | None = None,
34
+ webhook_url: str | None = None,
35
+ is_draft: bool = False,
36
+ ) -> Workflow:
37
+ """
38
+ Submits a logP workflow to the API.
39
+
40
+ :param initial_smiles: Molecule to predict logP for. Accepts a SMILES string or any
41
+ molecule type (RowanMolecule, stjames.Molecule, RDKit Mol, or dict). The molecule
42
+ must have a SMILES string associated with it, as this workflow is SMILES-based and
43
+ does not use 3D coordinates.
44
+ :param method: logP prediction method:
45
+ - "chemprop_sangster2026": chemprop v2 D-MPNN trained on experimental octanol/water
46
+ logP from the Sangster dataset.
47
+ - "crippen": RDKit implementation of the Wildman-Crippen atom-contribution model.
48
+ - "cosmors": Boltzmann-weighted COSMO-RS logP over a conformer ensemble.
49
+ :param name: Name of the workflow.
50
+ :param folder_uuid: UUID of the folder to store the workflow in.
51
+ :param folder: Folder object to store the workflow in.
52
+ :param max_credits: Maximum number of credits to use for the workflow.
53
+ :param webhook_url: URL that Rowan will POST to when the workflow completes.
54
+ :param is_draft: If True, submit the workflow as a draft without starting execution.
55
+ :returns: Workflow object representing the submitted workflow.
56
+ :raises ValueError: If the molecule has no SMILES associated with it.
57
+ :raises requests.HTTPError: if the request to the API fails.
58
+ """
59
+ if folder and folder_uuid:
60
+ raise ValueError("Provide either `folder` or `folder_uuid`, not both.")
61
+ if folder:
62
+ folder_uuid = folder.uuid
63
+ initial_smiles = extract_smiles(initial_smiles)
64
+ workflow = stjames.LogPWorkflow(initial_smiles=initial_smiles, logp_method=method)
65
+
66
+ data = {
67
+ "workflow_type": "logp",
68
+ "workflow_data": workflow.model_dump(mode="json"),
69
+ "initial_smiles": initial_smiles,
70
+ "name": name,
71
+ "folder_uuid": folder_uuid,
72
+ "max_credits": max_credits,
73
+ "webhook_url": webhook_url,
74
+ "is_draft": is_draft,
75
+ }
76
+
77
+ with api_client() as client:
78
+ response = client.post("/workflow", json=data)
79
+ response.raise_for_status()
80
+ return Workflow(**response.json())
@@ -62,7 +62,7 @@ class PoseAnalysisMDResult(WorkflowResult):
62
62
  return [
63
63
  TrajectoryResult(
64
64
  uuid=t.uuid,
65
- ligand_rmsd=t.ligand_rmsd,
65
+ ligand_rmsd=t.binder_rmsd,
66
66
  contacts=t.contacts,
67
67
  sasa=t.sasa,
68
68
  polar_sasa=t.polar_sasa,
@@ -104,7 +104,9 @@ class PoseAnalysisMDResult(WorkflowResult):
104
104
  if not (uuid := self.minimized_protein_uuid):
105
105
  return None
106
106
  if "minimized_protein" not in self._cache:
107
- self._cache["minimized_protein"] = retrieve_protein(uuid)
107
+ self._cache["minimized_protein"] = retrieve_protein(
108
+ uuid, workflow_uuid=self.workflow_uuid
109
+ )
108
110
  return self._cache["minimized_protein"]
109
111
 
110
112
  @property
@@ -165,7 +165,9 @@ class ProteinCofoldingResult(WorkflowResult):
165
165
  if not (uuid := self.predicted_structure_uuid):
166
166
  return None
167
167
  if "predicted_structure" not in self._cache:
168
- self._cache["predicted_structure"] = retrieve_protein(uuid)
168
+ self._cache["predicted_structure"] = retrieve_protein(
169
+ uuid, workflow_uuid=self.workflow_uuid
170
+ )
169
171
  return self._cache["predicted_structure"]
170
172
 
171
173
  def get_refined_structure(self) -> Protein | None:
@@ -178,7 +180,9 @@ class ProteinCofoldingResult(WorkflowResult):
178
180
  if not (uuid := self.predicted_refined_structure_uuid):
179
181
  return None
180
182
  if "refined_structure" not in self._cache:
181
- self._cache["refined_structure"] = retrieve_protein(uuid)
183
+ self._cache["refined_structure"] = retrieve_protein(
184
+ uuid, workflow_uuid=self.workflow_uuid
185
+ )
182
186
  return self._cache["refined_structure"]
183
187
 
184
188
  @property
@@ -4,7 +4,7 @@ from dataclasses import dataclass
4
4
  from pathlib import Path
5
5
 
6
6
  import stjames
7
- from stjames import GreedyClusteringSettings, KMeansClusteringSettings
7
+ from stjames import Binder, GreedyClusteringSettings, KMeansClusteringSettings
8
8
 
9
9
  from ..folder import Folder
10
10
  from ..protein import Protein, retrieve_protein
@@ -87,7 +87,9 @@ class ProteinMDResult(WorkflowResult):
87
87
  if not (uuid := self.minimized_protein_uuid):
88
88
  return None
89
89
  if "minimized_protein" not in self._cache:
90
- self._cache["minimized_protein"] = retrieve_protein(uuid)
90
+ self._cache["minimized_protein"] = retrieve_protein(
91
+ uuid, workflow_uuid=self.workflow_uuid
92
+ )
91
93
  return self._cache["minimized_protein"]
92
94
 
93
95
  @property
@@ -171,6 +173,10 @@ def submit_protein_md_workflow(
171
173
  ionic_strength_M: float = 0.0,
172
174
  water_buffer: float = 10.0,
173
175
  save_solvent: bool = False,
176
+ num_solvent_to_save: int | None = None,
177
+ binder: Binder | None = None,
178
+ protein_restraint_cutoff: float | None = None,
179
+ protein_restraint_constant: float = 100,
174
180
  analysis_interval_ps: float | None = None,
175
181
  clustering: KMeansClusteringSettings | GreedyClusteringSettings | None = None,
176
182
  validate_forcefield: bool = True,
@@ -198,13 +204,26 @@ def submit_protein_md_workflow(
198
204
  :param ionic_strength_M: ionic strength of the solution, in M (molar)
199
205
  :param water_buffer: amount of water to add around the protein, in A
200
206
  :param save_solvent: whether solvent should be saved
207
+ :param num_solvent_to_save: keep this many solvent molecules nearest the binder, or all if None;
208
+ only meaningful when save_solvent is True and a binder is present
209
+ :param binder: optional binder specification (protein chains and/or small molecules).
210
+ When set, per-frame MM/GBSA scores are computed against the whole binder.
211
+ Per-frame binder RMSD is populated only when the binder is a single component
212
+ (one small molecule → heavy-atom RMSD; one binder chain → backbone N/CA/C/O RMSD);
213
+ it is empty for multi-molecule, multi-chain, or combined chain+molecule binders.
214
+ :param protein_restraint_cutoff: cutoff distance from the binder past which Cα atoms are
215
+ harmonically restrained, in Å; None disables restraints
216
+ :param protein_restraint_constant: force constant for Cα backbone restraints, in kcal/mol/Ų
201
217
  :param analysis_interval_ps: Interval at which to compute per-frame SASA and polar SASA, in ps.
202
218
  None disables those analyses.
203
219
  :param clustering: How to cluster trajectory frames. None disables clustering; pass a
204
220
  KMeansClusteringSettings (num_clusters) or GreedyClusteringSettings (cutoff_angstrom).
205
221
  :param validate_forcefield: if True (default), validate the protein forcefield
206
222
  compatibility before submitting. Raises an error early if the protein cannot
207
- be parameterized or has clashing residues.
223
+ be parameterized or has clashing residues. Binder small molecules are skipped,
224
+ whether given by residue name or by index, since they are parameterized from their
225
+ SMILES rather than the protein forcefield; cofactors, metals, and glycans outside
226
+ the binder are still validated.
208
227
  :param name: Name of the workflow.
209
228
  :param folder_uuid: UUID of the folder to place the workflow in.
210
229
  :param folder: Folder object to store the workflow in.
@@ -222,7 +241,10 @@ def submit_protein_md_workflow(
222
241
  protein = protein.uuid
223
242
 
224
243
  if validate_forcefield:
225
- Protein(uuid=protein).validate_protein_forcefield()
244
+ exclude_residues = (
245
+ list(binder.small_molecules) if binder is not None and binder.small_molecules else None
246
+ )
247
+ Protein(uuid=protein).validate_protein_forcefield(exclude_residues=exclude_residues)
226
248
 
227
249
  workflow = stjames.ProteinMolecularDynamicsWorkflow(
228
250
  protein=protein,
@@ -238,6 +260,10 @@ def submit_protein_md_workflow(
238
260
  ionic_strength_M=ionic_strength_M,
239
261
  water_buffer=water_buffer,
240
262
  save_solvent=save_solvent,
263
+ num_solvent_to_save=num_solvent_to_save,
264
+ binder=binder,
265
+ protein_restraint_cutoff=protein_restraint_cutoff,
266
+ protein_restraint_constant=protein_restraint_constant,
241
267
  analysis_interval_ps=analysis_interval_ps,
242
268
  clustering=clustering,
243
269
  )
@@ -90,7 +90,9 @@ class RelativeBindingFreeEnergyPerturbationResult(WorkflowResult):
90
90
  @property
91
91
  def protein(self) -> Protein:
92
92
  """Prepared protein structure used as the simulation target."""
93
- return Protein(uuid=str(self._workflow.protein))
93
+ protein = Protein(uuid=str(self._workflow.protein))
94
+ protein._workflow_uuid = self.workflow_uuid
95
+ return protein
94
96
 
95
97
  @property
96
98
  def settings(self) -> TMDRBFESettings: