def test_init(self): sub = Substituent("COCH3") self.is_COCH3(sub) sub = Substituent("COCH3", targets=["C", "O", "H"]) self.is_COCH3(sub) return
def test_substitute(self): ref = Geometry(TestGeometry.benz_NO2_Cl) mol = Geometry(TestGeometry.benzene) mol.substitute(Substituent("NO2"), "12") mol.substitute(Substituent("Cl"), "11") self.assertTrue(validate(mol, ref))
def test_substitute(self): ref = Geometry(TestGeometry.benz_NO2_Cl) mol = Geometry(TestGeometry.benzene) mol.substitute(Substituent("NO2"), "12") mol.substitute(Substituent("Cl"), "11") rmsd = mol.RMSD(ref, align=True) self.assertTrue(rmsd < rmsd_tol(ref))
def test_detect_sub(self): mol = TestSubstituent.benz_NO2_Cl NO2 = mol.get_fragment("N", "C", as_object=True) sub = Substituent(NO2) self.is_NO2(sub) NO2 = mol.get_fragment("N", "C", copy=True) sub = Substituent(NO2) self.is_NO2(sub)
def test_align_to_bond(self): mol = TestSubstituent.benz_NO2_Cl bond = mol.bond("1", "12") sub = Substituent("NO2") sub.align_to_bond(bond) bond /= np.linalg.norm(bond) test_bond = sub.find("N")[0].coords - np.array([0.0, 0.0, 0.0]) test_bond /= np.linalg.norm(test_bond) self.assertTrue(np.linalg.norm(bond - test_bond) < 10**-8)
def test_minimize_torsion(self): geom = TestComponent.benz.copy() ref = Component("ref_files/minimize_torsion.xyz") geom.substitute(Substituent("tBu"), "12") geom.substitute(Substituent("Ph"), "10") geom.substitute(Substituent("OH"), "7") geom.minimize_sub_torsion() rmsd = geom.RMSD(ref, align=True) self.assertTrue(rmsd < 1)
def test_minimize_torsion(self): ref = Component( os.path.join(prefix, "ref_files", "minimize_torsion.xyz") ) geom = TestComponent.benz.copy() geom.substitute(Substituent("tBu"), "12") geom.substitute(Substituent("Ph"), "10") geom.substitute(Substituent("OH"), "7") geom.minimize_sub_torsion() self.assertTrue(validate(geom, ref, heavy_only=True))
def register_selector_menus(session): from PyQt5.QtWidgets import QAction from AaronTools.const import ELEMENTS from AaronTools.substituent import Substituent add_submenu = session.ui.main_window.add_select_submenu add_selector = session.ui.main_window.add_menu_selector substituent_menu = add_submenu(['Che&mistry'], 'Substituents') for sub in Substituent.list(): if sub in ELEMENTS: # print(sub, "in ELEMENTS") continue if not sub[0].isalpha(): # print(sub, "startswith non-alpha") continue if len(sub) > 1 and any(not (c.isalnum() or c in "+-") for c in sub[1:]): # print(sub, "contains non-alphanumeric character") continue add_selector(substituent_menu, sub, sub) mw = session.ui.main_window structure_menu = add_submenu([], '&Structure') structure_menu.addAction(QAction("Connected", mw))
def open_substituents(self): for row in self.sub_table.table.selectionModel().selectedRows(): if self.sub_table.table.isRowHidden(row.row()): continue sub_name = row.data() substituent = Substituent(sub_name, name=sub_name) chimera_substituent = ResidueCollection(substituent).get_chimera( self.session) self.session.models.add([chimera_substituent]) apply_seqcrow_preset(chimera_substituent, fallback="Ball-Stick-Endcap") if self.showSubGhostBool: color = self.sub_color.get_color() color = [c / 255. for c in color] self.settings.ghost_connection_color = tuple(color) bild_obj = ghost_connection_highlight(substituent, color, self.session) self.session.models.add(bild_obj, parent=chimera_substituent)
def substitute(self, sub, target, attached_to=None, minimize=True): if not isinstance(sub, Substituent): sub = Substituent(sub) for key, val in self.components.items(): for comp in val: try: target = comp.find_exact(target) except LookupError: # keep looking continue # found! do substitution and return comp.substitute(sub, target, attached_to) # update tags for a in comp.atoms: a.add_tag(key) # remove old from conf_spec for t in target: if t in self.conf_spec: del self.conf_spec[t] # add new to conf_spec if sub.conf_num is not None and sub.conf_num > 1: self.conf_spec[sub.atoms[0]] = [1, []] self.rebuild() self.detect_components() if minimize: self.minimize() return
def _decode_substituent(self, obj): kwargs = {} for key in ["name", "end", "conf_num", "conf_angle"]: kwargs[key] = obj[key] ranks = [a._rank for a in obj["atoms"]] obj = self._decode_geometry(obj) for a, r in zip(obj.atoms, ranks): a._rank = r return Substituent(obj, **kwargs)
def initialize(session, bundle_info): from SEQCROW import settings as seqcrow_settings seqcrow_settings.settings = settings._SEQCROWSettings( session, "SEQCROW") if session.ui.is_gui: from .presets import seqcrow_bse, seqcrow_s, seqcrow_vdw, indexLabel session.presets.add_presets( "SEQCROW", {"ball-stick-endcap": lambda p=seqcrow_bse: p(session)}) session.presets.add_presets( "SEQCROW", {"sticks": lambda p=seqcrow_s: p(session)}) session.presets.add_presets( "SEQCROW", {"VDW": lambda p=seqcrow_vdw: p(session)}) session.presets.add_presets( "SEQCROW", {"index labels": lambda p=indexLabel: p(session)}) session.ui.triggers.add_handler('ready', lambda *args, ses=session: settings .register_settings_options(ses)) session.ui.triggers.add_handler( 'ready', lambda *args, ses=session: _SEQCROW_API. register_selector_menus(ses)) #apply AARONLIB setting if seqcrow_settings.settings.AARONLIB is not None: os.environ['AARONLIB'] = seqcrow_settings.settings.AARONLIB session.seqcrow_settings = seqcrow_settings from AaronTools.const import ELEMENTS from AaronTools.substituent import Substituent #register selectors from the user's personal library for sub in Substituent.list(): if sub in ELEMENTS: # print(sub, "in ELEMENTS") continue if not sub[0].isalpha(): # print(sub, "startswith non-alpha") continue if len(sub) > 1 and any(not (c.isalnum() or c in "+-") for c in sub[1:]): # print(sub, "contains non-alphanumeric character") continue if not any( [selector.name == sub for selector in bundle_info.selectors]): si = SelectorInfo(sub, atomic=True, synopsis="%s substituent" % sub) bundle_info.selectors.append(si) #need to reregister selectors b/c ^ that bypassed the bundle_info.xml or something bundle_info._register_selectors(session.logger)
def test_substituent(self): try: import rdkit sub = Substituent.from_string( "acetyl", form="iupac", strict_use_rdkit=True ) self.is_COCH3(sub) with self.assertLogs(Substituent.LOG, level="WARNING"): sub = Substituent.from_string( "nitro", form="iupac", strict_use_rdkit=True ) self.is_NO2(sub) sub = Substituent.from_string( "O=[N.]=O", form="smiles", strict_use_rdkit=True ) self.is_NO2(sub) sub = Substituent.from_string( "O=[N]=O", form="smiles", strict_use_rdkit=True ) self.is_NO2(sub) except (ImportError, ModuleNotFoundError): # I still want to test CACTVS things because sometimes they change stuff # that breaks our stuff if any( user == os.getenv("USER", os.getenv("USERNAME", False)) for user in ["ajs99778", "normn"] ): sub = Substituent.from_string("acetyl", form="iupac") print(sub.write(outfile=False)) self.is_COCH3(sub, thresh=0.3) sub = Substituent.from_string("nitro", form="iupac") print(sub.write(outfile=False)) self.is_NO2(sub) sub = Substituent.from_string("O=[N.]=O", form="smiles") print(sub.write(outfile=False)) self.is_NO2(sub) sub = Substituent.from_string("O=[N]=O", form="smiles") print(sub.write(outfile=False)) self.is_NO2(sub) else: self.skipTest("RDKit not installed, CACTVS is not tested")
def test_substitute(self): mol = TestComponent.benz.copy() benz_Cl = TestComponent.benz_Cl.copy() benz_NO2_Cl = TestComponent.benz_NO2_Cl.copy() benz_OH_Cl = TestComponent.benz_OH_Cl.copy() benz_Ph_Cl = TestComponent.benz_Ph_Cl.copy() mol.substitute(Substituent("Cl"), "11") res = validate(mol, benz_Cl) self.assertTrue(res) mol.substitute(Substituent("NO2"), "12", "1") res = validate(mol, benz_NO2_Cl, sort=True) self.assertTrue(res) mol.substitute(Substituent("OH"), "NO2") res = validate(mol, benz_OH_Cl, sort=True) self.assertTrue(res) mol.substitute(Substituent("Ph"), ["12", "12.*"]) res = validate(mol, benz_Ph_Cl, sort=True, thresh="loose") self.assertTrue(res)
def test_substitute(self): mol = TestComponent.benz.copy() benz_Cl = TestComponent.benz_Cl.copy() benz_NO2_Cl = TestComponent.benz_NO2_Cl.copy() benz_OH_Cl = TestComponent.benz_OH_Cl.copy() benz_Ph_Cl = TestComponent.benz_Ph_Cl.copy() mol.substitute(Substituent("Cl"), "11") rmsd = mol.RMSD(benz_Cl, sort=True) self.assertTrue(rmsd < rmsd_tol(benz_Cl)) mol.substitute(Substituent("NO2"), "12", "1") rmsd = mol.RMSD(benz_NO2_Cl, sort=True) self.assertTrue(rmsd < rmsd_tol(benz_NO2_Cl)) mol.substitute(Substituent("OH"), "NO2") rmsd = mol.RMSD(benz_OH_Cl, sort=True) self.assertTrue(rmsd < rmsd_tol(benz_OH_Cl)) mol.substitute(Substituent("Ph"), "12.*") rmsd = mol.RMSD(benz_Ph_Cl) self.assertTrue(rmsd < rmsd_tol(benz_Ph_Cl))
def register_selectors(logger, name): if name == "tm": register_selector("tm", tm_selector, logger, desc="transition metals") elif any(name == sub for sub in Substituent.list()): register_selector( name, lambda sess, models, results, sub_name=name: substituent_selection( sess, sub_name, models, results), logger, desc="substituent %s" % name) elif name == "connected": register_selector("connected", all_connected_selector, logger, desc="fragment connected to selected atoms")
def __init__( self, structure, name="", comment=None, tag=None, to_center=None, key_atoms=None, detect_backbone=True, ): """ comp is either a file, a geometry, or an atom list """ super().__init__() self.name = name self.comment = comment self.other = {} self.substituents = [] self.backbone = None self.key_atoms = [] if isinstance(structure, str) and not os.access(structure, os.R_OK): for ext in read_types: if structure.endswith(".%s" % ext): structure = structure[: -(1 + len(ext))] for lib in [Component.AARON_LIBS, Component.BUILTIN]: if not os.path.exists(lib): continue flig = None for f in os.listdir(lib): name, ext = os.path.splitext(f) if not any(".%s" % x == ext for x in read_types): continue match = structure == name if match: flig = os.path.join(lib, f) break if flig: break else: try: structure = Substituent(structure) Component.FROM_SUBSTITUENTS.add(structure.name) self.__init__(structure, comment="K:1") return except Exception: raise FileNotFoundError( "Cannot find ligand in library:", structure ) structure = flig super().__init__(structure, name, comment) if tag is not None: for a in self.atoms: a.add_tag(tag) self.other = self.parse_comment() try: self.key_atoms = self.find("key") except LookupError: if "key_atoms" in self.other: self.key_atoms = [ self.atoms[i] for i in self.other["key_atoms"] ] if key_atoms is not None: self.key_atoms = self.find(key_atoms) for a in self.key_atoms: a.tags.add("key") if detect_backbone: self.detect_backbone(to_center) self.rebuild()
def _build_ui(self): layout = QGridLayout() self.alchemy_tabs = QTabWidget() #substitute substitute_tab = QWidget() substitute_layout = QGridLayout(substitute_tab) sublabel = QLabel("substituent name:") substitute_layout.addWidget(sublabel, 0, 0, Qt.AlignVCenter) self.subname = QLineEdit() # self.subname.setText("Et") sub_completer = NameCompleter(Substituent.list(), self.subname) self.subname.setCompleter(sub_completer) self.subname.setToolTip("name of substituent in the AaronTools library or your personal library\nseparate names with commas and uncheck 'modify selected structure' to create several structures") substitute_layout.addWidget(self.subname, 0, 1, Qt.AlignVCenter) open_sub_lib = QPushButton("from library...") open_sub_lib.clicked.connect(self.open_sub_selector) substitute_layout.addWidget(open_sub_lib, 0, 2, Qt.AlignTop) substitute_layout.addWidget(QLabel("modify selected structure:"), 1, 0, 1, 1, Qt.AlignVCenter) self.close_previous_sub = QCheckBox() self.close_previous_sub.setToolTip("checked: selected structure will be modified\nunchecked: new model will be created for the modified structure") self.close_previous_sub.setChecked(self.settings.modify) self.close_previous_sub.stateChanged.connect(self.close_previous_change) substitute_layout.addWidget(self.close_previous_sub, 1, 1, 1, 2, Qt.AlignTop) substitute_layout.addWidget(QLabel("relax substituent:"), 2, 0, 1, 1, Qt.AlignVCenter) self.minimize = QCheckBox() self.minimize.setToolTip("spin the added substituents to try to minimize the LJ potential energy") self.minimize.setChecked(self.settings.minimize) substitute_layout.addWidget(self.minimize, 2, 1, 1, 1, Qt.AlignTop) substitute_layout.addWidget(QLabel("guess previous substituent:"), 3, 0, 1, 1, Qt.AlignVCenter) self.guess_old = QCheckBox() self.guess_old.setToolTip("checked: leave the longest connected fragment in the residue\nunchecked: previous substituent must be selected") self.guess_old.setChecked(self.settings.guess) self.guess_old.stateChanged.connect(lambda state, settings=self.settings: settings.__setattr__("guess", True if state == Qt.Checked else False)) substitute_layout.addWidget(self.guess_old, 3, 1, 1, 2, Qt.AlignTop) substitute_layout.addWidget(QLabel("new residue:"), 5, 0, 1, 1, Qt.AlignVCenter) self.new_residue = QCheckBox() self.new_residue.setToolTip("put the new substituent in its own residue instead\nof adding it to the residue of the old substituent") self.new_residue.setChecked(self.settings.new_residue) self.new_residue.stateChanged.connect(lambda state, settings=self.settings: settings.__setattr__("new_residue", True if state == Qt.Checked else False)) substitute_layout.addWidget(self.new_residue, 5, 1, 1, 2, Qt.AlignTop) substitute_layout.addWidget(QLabel("use distance names:"), 4, 0, 1, 1, Qt.AlignVCenter) self.use_greek = QCheckBox() self.use_greek.setChecked(self.settings.use_greek) self.use_greek.setToolTip("indicate distance from point of attachment with atom name") substitute_layout.addWidget(self.use_greek, 4, 1, 1, 1, Qt.AlignTop) substitute_layout.addWidget(QLabel("change residue name:"), 6, 0, 1, 1, Qt.AlignVCenter) self.new_sub_name = QLineEdit() self.new_sub_name.setToolTip("change name of modified residues") self.new_sub_name.setPlaceholderText("leave blank to keep current") substitute_layout.addWidget(self.new_sub_name, 6, 1, 1, 2, Qt.AlignTop) substitute_button = QPushButton("substitute current selection") substitute_button.clicked.connect(self.do_substitute) substitute_layout.addWidget(substitute_button, 7, 0, 1, 3, Qt.AlignTop) self.substitute_button = substitute_button substitute_layout.setRowStretch(0, 0) substitute_layout.setRowStretch(1, 0) substitute_layout.setRowStretch(2, 0) substitute_layout.setRowStretch(3, 0) substitute_layout.setRowStretch(4, 0) substitute_layout.setRowStretch(5, 0) substitute_layout.setRowStretch(6, 0) substitute_layout.setRowStretch(7, 1) #map ligand maplig_tab = QWidget() maplig_layout = QGridLayout(maplig_tab) liglabel = QLabel("ligand name:") maplig_layout.addWidget(liglabel, 0, 0, Qt.AlignVCenter) self.ligname = QLineEdit() lig_completer = NameCompleter(Component.list(), self.ligname) self.ligname.setCompleter(lig_completer) self.ligname.setToolTip("name of ligand in the AaronTools library or your personal library\nseparate names with commas and uncheck 'modify selected structure' to create several structures") maplig_layout.addWidget(self.ligname, 0, 1, Qt.AlignVCenter) open_lig_lib = QPushButton("from library...") open_lig_lib.clicked.connect(self.open_lig_selector) maplig_layout.addWidget(open_lig_lib, 0, 2, Qt.AlignTop) maplig_layout.addWidget(QLabel("modify selected structure:"), 1, 0, 1, 1, Qt.AlignVCenter) self.close_previous_lig = QCheckBox() self.close_previous_lig.setToolTip("checked: selected structure will be modified\nunchecked: new model will be created for the modified structure") self.close_previous_lig.setChecked(self.settings.modify) self.close_previous_lig.stateChanged.connect(self.close_previous_change) maplig_layout.addWidget(self.close_previous_lig, 1, 1, 1, 2, Qt.AlignTop) maplig_button = QPushButton("swap ligand with selected coordinating atoms") maplig_button.clicked.connect(self.do_maplig) maplig_layout.addWidget(maplig_button, 2, 0, 1, 3, Qt.AlignTop) self.maplig_button = maplig_button start_structure_button = QPushButton("place in:") self.lig_model_selector = ModelComboBox(self.session, addNew=True) start_structure_button.clicked.connect(self.do_new_lig) maplig_layout.addWidget(start_structure_button, 3, 0, 1, 1, Qt.AlignTop) maplig_layout.addWidget(self.lig_model_selector, 3, 1, 1, 2, Qt.AlignTop) maplig_layout.setRowStretch(0, 0) maplig_layout.setRowStretch(1, 0) maplig_layout.setRowStretch(2, 0) maplig_layout.setRowStretch(3, 1) #close ring closering_tab = QWidget() closering_layout = QGridLayout(closering_tab) ringlabel = QLabel("ring name:") closering_layout.addWidget(ringlabel, 0, 0, Qt.AlignVCenter) self.ringname = QLineEdit() ring_completer = NameCompleter(Ring.list(), self.ringname) self.ringname.setCompleter(ring_completer) self.ringname.setToolTip("name of ring in the AaronTools library or your personal library\nseparate names with commas and uncheck 'modify selected structure' to create several structures") closering_layout.addWidget(self.ringname, 0, 1, Qt.AlignVCenter) open_ring_lib = QPushButton("from library...") open_ring_lib.clicked.connect(self.open_ring_selector) closering_layout.addWidget(open_ring_lib, 0, 2, Qt.AlignTop) closering_layout.addWidget(QLabel("modify selected structure:"), 1, 0, 1, 1, Qt.AlignVCenter) self.close_previous_ring = QCheckBox() self.close_previous_ring.setToolTip("checked: selected structure will be modified\nunchecked: new model will be created for the modified structure") self.close_previous_ring.setChecked(self.settings.modify) self.close_previous_ring.stateChanged.connect(self.close_previous_change) closering_layout.addWidget(self.close_previous_ring, 1, 1, 1, 2, Qt.AlignTop) closering_layout.addWidget(QLabel("try multiple:"), 2, 0, 1, 1, Qt.AlignVCenter) self.minimize_ring = QCheckBox() self.minimize_ring.setToolTip("try to use other versions of this ring in the library to find the one that fits best") self.minimize_ring.setChecked(self.settings.minimize_ring) closering_layout.addWidget(self.minimize_ring, 2, 1, 1, 2, Qt.AlignTop) closering_layout.addWidget(QLabel("new residue name:"), 3, 0, 1, 1, Qt.AlignVCenter) self.new_ring_name = QLineEdit() self.new_ring_name.setToolTip("change name of modified residues") self.new_ring_name.setPlaceholderText("leave blank to keep current") closering_layout.addWidget(self.new_ring_name, 3, 1, 1, 2, Qt.AlignTop) closering_button = QPushButton("put a ring on current selection") closering_button.clicked.connect(self.do_fusering) closering_layout.addWidget(closering_button, 4, 0, 1, 3, Qt.AlignTop) self.closering_button = closering_button start_structure_button = QPushButton("place in:") self.ring_model_selector = ModelComboBox(self.session, addNew=True) start_structure_button.clicked.connect(self.do_new_ring) closering_layout.addWidget(start_structure_button, 5, 0, 1, 1, Qt.AlignTop) closering_layout.addWidget(self.ring_model_selector, 5, 1, 1, 2, Qt.AlignTop) closering_layout.setRowStretch(0, 0) closering_layout.setRowStretch(1, 0) closering_layout.setRowStretch(2, 0) closering_layout.setRowStretch(3, 0) closering_layout.setRowStretch(4, 0) closering_layout.setRowStretch(5, 1) #change element changeelement_tab = QWidget() changeelement_layout = QFormLayout(changeelement_tab) self.element = ElementButton("C", single_state=True) self.element.clicked.connect(self.open_ptable) changeelement_layout.addRow("element:", self.element) self.vsepr = QComboBox() self.vsepr.addItems([ "do not change", # 0 "linear (1 bond)", # 1 "linear (2 bonds)", # 2 "trigonal planar (2 bonds)", # 3 "tetrahedral (2 bonds)", # 4 "trigonal planar", # 5 "tetrahedral (3 bonds)", # 6 "T-shaped", # 7 "trigonal pyramidal", # 8 "tetrahedral", # 9 "sawhorse", #10 "seesaw", #11 "square planar", #12 "trigonal bipyramidal", #13 "square pyramidal", #14 "pentagonal", #15 "octahedral", #16 "hexagonal", #17 "trigonal prismatic", #18 "pentagonal pyramidal", #19 "capped octahedral", #20 "capped trigonal prismatic", #21 "heptagonal", #22 "hexagonal pyramidal", #23 "pentagonal bipyramidal", #24 "biaugmented trigonal prismatic", #25 "cubic", #26 "elongated trigonal bipyramidal", #27 "hexagonal bipyramidal", #28 "heptagonal pyramidal", #29 "octagonal", #30 "square antiprismatic", #31 "trigonal dodecahedral", #32 "capped cube", #33 "capped square antiprismatic", #34 "enneagonal", #35 "heptagonal bipyramidal", #36 "hula-hoop", #37 "triangular cupola", #38 "tridiminished icosahedral", #39 "muffin", #40 "octagonal pyramidal", #41 "tricapped trigonal prismatic", #42 ]) self.vsepr.setCurrentIndex(9) self.vsepr.insertSeparator(33) self.vsepr.insertSeparator(25) self.vsepr.insertSeparator(20) self.vsepr.insertSeparator(16) self.vsepr.insertSeparator(13) self.vsepr.insertSeparator(8) self.vsepr.insertSeparator(5) self.vsepr.insertSeparator(2) self.vsepr.insertSeparator(1) self.vsepr.insertSeparator(0) changeelement_layout.addRow("geometry:", self.vsepr) self.change_bonds = QCheckBox() self.change_bonds.setChecked(self.settings.change_bonds) changeelement_layout.addRow("adjust bond lengths:", self.change_bonds) change_element_button = QPushButton("change selected elements") change_element_button.clicked.connect(self.do_change_element) changeelement_layout.addRow(change_element_button) self.change_element_button = change_element_button start_structure_button = QPushButton("place in:") self.model_selector = ModelComboBox(self.session, addNew=True) start_structure_button.clicked.connect(self.do_new_atom) changeelement_layout.addRow(start_structure_button, self.model_selector) delete_atoms_button = QPushButton("delete selected atoms") delete_atoms_button.clicked.connect(self.delete_atoms) changeelement_layout.addRow(delete_atoms_button) self.alchemy_tabs.addTab(substitute_tab, "substitute") self.alchemy_tabs.addTab(maplig_tab, "swap ligand") self.alchemy_tabs.addTab(closering_tab, "fuse ring") self.alchemy_tabs.addTab(changeelement_tab, "change element") layout.addWidget(self.alchemy_tabs) self.tool_window.ui_area.setLayout(layout) self.tool_window.manage(None)
class TestFromString(TestWithTimer): """ these only get run if RDKit is installed molecules can be fetched from CATCVS, but there can be discrepencies that make these tests "fail" """ COCH3 = Substituent("COCH3") NO2 = Substituent("NO2") benzene = Ring("benzene") chiral_geom = Geometry( os.path.join(prefix, "test_files", "chiral_ring.xyz") ) def is_COCH3(self, sub, thresh=0.03): ref = TestFromString.COCH3 ref.refresh_connected() sub.refresh_connected() ref.refresh_ranks() ref.refresh_ranks() ref.atoms = ref.reorder(start=ref.atoms[0])[0] sub.atoms = sub.reorder(start=sub.atoms[0])[0] self.assertTrue( validate( sub, ref, thresh=thresh, heavy_only=True, sort=False, debug=False, ) ) def is_NO2(self, sub): ref = TestFromString.NO2 self.assertTrue(validate(sub, ref, thresh=2e-1)) def test_substituent(self): try: import rdkit sub = Substituent.from_string( "acetyl", form="iupac", strict_use_rdkit=True ) self.is_COCH3(sub) with self.assertLogs(Substituent.LOG, level="WARNING"): sub = Substituent.from_string( "nitro", form="iupac", strict_use_rdkit=True ) self.is_NO2(sub) sub = Substituent.from_string( "O=[N.]=O", form="smiles", strict_use_rdkit=True ) self.is_NO2(sub) sub = Substituent.from_string( "O=[N]=O", form="smiles", strict_use_rdkit=True ) self.is_NO2(sub) except (ImportError, ModuleNotFoundError): # I still want to test CACTVS things because sometimes they change stuff # that breaks our stuff if any( user == os.getenv("USER", os.getenv("USERNAME", False)) for user in ["ajs99778", "normn"] ): sub = Substituent.from_string("acetyl", form="iupac") print(sub.write(outfile=False)) self.is_COCH3(sub, thresh=0.3) sub = Substituent.from_string("nitro", form="iupac") print(sub.write(outfile=False)) self.is_NO2(sub) sub = Substituent.from_string("O=[N.]=O", form="smiles") print(sub.write(outfile=False)) self.is_NO2(sub) sub = Substituent.from_string("O=[N]=O", form="smiles") print(sub.write(outfile=False)) self.is_NO2(sub) else: self.skipTest("RDKit not installed, CACTVS is not tested") def test_geometry(self): try: import rdkit geom = Geometry.from_string( "(1R,2R)-1-Chloro-2-methylcyclohexane", form="iupac" ) ref = TestFromString.chiral_geom # really loose threshhold b/c rdkit can give a boat cyclohexane... self.assertTrue(validate(geom, ref, thresh=0.35, heavy_only=True)) except (ImportError, ModuleNotFoundError): if any( user == os.getenv("USER", os.getenv("USERNAME", False)) for user in ["ajs99778", "normn"] ): geom = Geometry.from_string( "(1R,2R)-1-Chloro-2-methylcyclohexane", form="iupac" ) ref = TestFromString.chiral_geom # really loose threshhold b/c rdkit can give a boat cyclohexane... self.assertTrue( validate(geom, ref, thresh=0.35, heavy_only=True) ) else: self.skipTest("RDKit not installed, CACTVS is not tested") def test_ring(self): try: import rdkit ring = Ring.from_string( "benzene", end_length=1, end_atom="C", form="iupac" ) ref = self.benzene self.assertTrue(validate(ring, ref, thresh="loose")) except ImportError: if any( user == os.getenv("USER", os.getenv("USERNAME", False)) for user in ["ajs99778", "normn"] ): ring = Ring.from_string( "benzene", end_length=1, end_atom="C", form="iupac" ) print(ring.comment) ref = self.benzene self.assertTrue( validate(ring, ref, thresh="loose", debug=True) ) else: self.skipTest("RDKit not installed, CACTVS is not tested")
def libadd_substituent(self): """add ligand to library or open it in a new model""" selection = selected_atoms(self.session) if not selection.single_structure: raise RuntimeError("selected atoms must be on the same model") residues = [] for atom in selection: if atom.residue not in residues: residues.append(atom.residue) rescol = ResidueCollection(selection[0].structure, convert_residues=residues) substituent_atoms = [ atom for atom in rescol.atoms if atom.chix_atom in selection ] start = None avoid = None for atom in substituent_atoms: for atom2 in atom.connected: if atom2 not in substituent_atoms: if start is None: start = atom avoid = atom2 else: raise RuntimeError( "substituent must only have one connection to the molecule" ) if start is None: raise RuntimeError( "substituent is not connected to a larger molecule") substituent_atoms.remove(start) substituent_atoms.insert(0, start) sub_name = self.sub_name.text() confs = self.sub_confs.value() angle = self.sub_angle.value() comment = "CF:%i,%i" % (confs, angle) if len(sub_name) == 0: sub = Substituent(substituent_atoms, name="test", conf_num=confs, conf_angle=angle) else: sub = Substituent(substituent_atoms, name=sub_name, conf_num=confs, conf_angle=angle) sub.comment = comment #align substituent bond to x axis sub.coord_shift(-avoid.coords) x_axis = np.array([1., 0., 0.]) n = np.linalg.norm(start.coords) vb = start.coords / n d = np.linalg.norm(vb - x_axis) theta = np.arccos((d**2 - 2) / -2) vx = np.cross(vb, x_axis) sub.rotate(vx, theta) add = False if len(sub_name) == 0: chimerax_sub = ResidueCollection(sub).get_chimera(self.session) chimerax_sub.name = "substituent preview" self.session.models.add([chimerax_sub]) bild_obj = ghost_connection_highlight( sub, [0.60784, 0.145098, 0.70196, 0.5], self.session) self.session.models.add(bild_obj, parent=chimerax_sub) else: check_aaronlib_dir() filename = os.path.join(AARONLIB, "Subs", sub_name + ".xyz") if os.path.exists(filename): exists_warning = QMessageBox() exists_warning.setIcon(QMessageBox.Warning) exists_warning.setText( "%s already exists.\nWould you like to overwrite?" % filename) exists_warning.setStandardButtons(QMessageBox.Yes | QMessageBox.No) rv = exists_warning.exec_() if rv == QMessageBox.Yes: add = True else: self.tool_window.status( "%s has not been added to substituent library" % sub_name) else: add = True if add: sub.write(outfile=filename) self.tool_window.status("%s added to substituent library" % sub_name) register_selectors(self.session.logger, sub_name) if self.session.ui.is_gui: if (sub_name not in ELEMENTS and sub_name[0].isalpha() and (len(sub_name) > 1 and not any(not (c.isalnum() or c in "+-") for c in sub_name[1:]))): add_submenu = self.session.ui.main_window.add_select_submenu add_selector = self.session.ui.main_window.add_menu_selector substituent_menu = add_submenu(['Che&mistry'], 'Substituents') add_selector(substituent_menu, sub_name, sub_name)
"--output", type=str, default=False, required=False, metavar="output destination", dest="outfile", help="output destination\n" + "$INFILE will be replaced with the name of the input file\n" + "Default: stdout" ) args = substitute_parser.parse_args() if args.list_avail: s = "" for i, name in enumerate(sorted(Substituent.list())): s += "%-20s" % name # if (i + 1) % 3 == 0: if (i + 1) % 1 == 0: s += "\n" print(s.strip()) sys.exit(0) for infile in glob_files(args.infile, parser=substitute_parser): if isinstance(infile, str): if args.input_format is not None: f = FileReader((infile, args.input_format, infile)) else: f = FileReader(infile) else:
def sterimol( session, selection, radii="UMN", showVectors=True, showRadii=True, LCorrection="FORTRAN", return_values=False ): models, attached = avoidTargets(session.logger, selection) radii = radii.lower() old_L = False if LCorrection.upper() == "FORTRAN": old_L = True model_names = [] targets = [] datas = [] info = "<pre>model\tsubstituent atom\tB1\tB2\tB3\tB4\tB5\tL\n" # if return_values: # if len(models.keys()) > 1: # raise RuntimeError("only one substituent may be selected") # if any(len(models[key]) > 1 for key in models.keys()): # raise RuntimeError("only one substituent may be selected") for model in models: rescol = ResidueCollection(model, refresh_ranks=False) for res in models[model]: for target in models[model][res]: end_atomspec = AtomSpec(attached[target].atomspec) start_atomspec = AtomSpec(target.atomspec) sub_atoms = rescol.get_fragment(start_atomspec, end_atomspec) sub = Substituent( sub_atoms, end=rescol.find_exact(end_atomspec)[0], detect=False, ) data = sub.sterimol( return_vector=True, radii=radii, old_L=old_L, ) l = np.linalg.norm(data["L"][1] - data["L"][0]) b1 = np.linalg.norm(data["B1"][1] - data["B1"][0]) b2 = np.linalg.norm(data["B2"][1] - data["B2"][0]) b3 = np.linalg.norm(data["B3"][1] - data["B3"][0]) b4 = np.linalg.norm(data["B4"][1] - data["B4"][0]) b5 = np.linalg.norm(data["B5"][1] - data["B5"][0]) if showVectors: for key, color in zip( ["B1", "B2", "B3", "B4", "B5", "L"], ["black", "green", "purple", "orange", "red", "blue"] ): start, end = data[key] s = ".color %s\n" % color s += ".note Sterimol %s\n" % key s += ".arrow %6.3f %6.3f %6.3f %6.3f %6.3f %6.3f\n" % (*start, *end) stream = BytesIO(bytes(s, "utf-8")) bild_obj, status = read_bild(session, stream, "Sterimol %s" % key) session.models.add(bild_obj, parent=model) if showRadii: s = ".note radii\n" s += ".transparency 75\n" color = None for atom in sub.atoms: chix_atom = atom.chix_atom if radii == "umn": r = VDW_RADII[chix_atom.element.name] elif radii == "bondi": r = BONDI_RADII[chix_atom.element.name] if color is None or chix_atom.color != color: color = chix_atom.color rgb = [x/255. for x in chix_atom.color] rgb.pop(-1) s += ".color %f %f %f\n" % tuple(rgb) s += ".sphere %f %f %f %f\n" % (*chix_atom.coord, r) stream = BytesIO(bytes(s, "utf-8")) bild_obj, status = read_bild(session, stream, "Sterimol radii") session.models.add(bild_obj, parent=model) model_name = get_filename(model.name, include_parent_dir=False) info += "%-16s\t%-11s\t%.2f\t%.2f\t%.2f\t%.2f\t%.2f\t%.2f\n" % ( model_name, target.atomspec, b1, b2, b3, b4, b5, l ) model_names.append(model_name) targets.append(target.atomspec) datas.append(data) info = info.strip() info += "</pre>" if not return_values: session.logger.info(info, is_html=True) if return_values: return model_names, targets, datas
def substitute_list(session): s = "" for subname in Substituent.list(): s += "%s\n" % subname session.logger.info(s.strip())
def substitute( session, selection=None, substituents=None, newName=None, guessAttachment=True, modify=True, minimize=False, useRemoteness=False, available=False, newResidue=False, ): if available: substitute_list(session) return if not selection: selection = selected_atoms(session) if not substituents: session.logger.error("missing required \"substituents\" argument") return attached = {} if newName is None: newName = [None for s in substituents] elif any(len(name.strip()) > 4 for name in newName): raise RuntimeError("residue names must be 4 characters or less") elif not all(name.isalnum() for name in newName): raise RuntimeError("invalid residue name: %s" % " ".join(newName)) elif len(substituents) != len(newName): raise RuntimeError( "number of substituents is not the same as the number of new names" ) if not guessAttachment: models, attached = avoidTargets(selection) else: models, attached = guessAttachmentTargets(selection, session) first_pass = True new_structures = [] for ndx, subname in enumerate(substituents): subname = subname.strip() sub = Substituent(subname) # when minimizing, we only want to deal with residues that are close to the substituent # determine the size of the new substituent to limit this if minimize: size = 5 for atom in sub.atoms: d = np.linalg.norm(atom.coords) if d > size: size = d for model in models: if modify and first_pass: conv_res = [] for res in models[model]: if res not in conv_res: conv_res.append(res) if minimize: for chix_res in model.residues: if chix_res in conv_res: continue added_res = False for atom in chix_res.atoms: for target in models[model][res]: d = np.linalg.norm(atom.coord - target.coord) if d < (size + 3): conv_res.append(chix_res) added_res = True break if added_res: break rescol = ResidueCollection(model, convert_residues=conv_res) for res in models[model]: for target in models[model][res]: if attached is not None: end = AtomSpec(attached[target].atomspec) else: end = None # call substitute on the ResidueCollection b/c we need to see # the other residues if minimize=True rescol.substitute( sub.copy(), AtomSpec(target.atomspec), attached_to=end, minimize=minimize, use_greek=useRemoteness, new_residue=newResidue, new_name=newName[ndx], ) rescol.update_chix(model) elif modify and not first_pass: raise RuntimeError("only the first model can be replaced") else: model_copy = model.copy() conv_res = [ model_copy.residues[i] for i in [model.residues.index(res) for res in models[model]] ] # modifying_residues = [model_copy.residues[i] for i in [model.residues.index(res) for res in models[model]]] modifying_residues = [r for r in conv_res] if minimize: for chix_res in model_copy.residues: if chix_res in conv_res: continue added_res = False for res in models[model]: for target in models[model][res]: for atom in chix_res.atoms: d = np.linalg.norm(atom.coord - target.coord) if d < (size + 3): conv_res.append(chix_res) added_res = True break if added_res: break if added_res: break rescol = ResidueCollection(model_copy, convert_residues=conv_res) for residue, res in zip(modifying_residues, models[model]): for target in models[model][res]: if attached is not None: end = AtomSpec(model_copy.atoms[model.atoms.index( attached[target])].atomspec) else: end = None rescol.substitute( sub.copy(), AtomSpec(model_copy.atoms[model.atoms.index( target)].atomspec), attached_to=end, minimize=minimize, use_greek=useRemoteness, new_residue=newResidue, new_name=newName[ndx], ) rescol.update_chix(model_copy) new_structures.append(model_copy) first_pass = False if not modify: session.models.add(new_structures)
def substituent_selection(session, sub_name, models, results): #TODO: optimize - or cheat and use cython or something #TODO: make it so it doesn't select things with just an H bonded to them # e.g. sel OH should not select water molecules # probably do a get_all_connected for each fragment and # check if all_connected.subtract(Atoms[atom]).subtract(frag) leave just an H atoms = Atoms() sub = Substituent(sub_name) chix_sub = ResidueCollection(sub).get_chimera(session) sub_elements = sorted(chix_sub.atoms.elements.names) sub_ranks = canonical_rank(Atoms(chix_sub.atoms)) sorted_sub_atoms = [ x for _, x in sorted(zip(sub_ranks, chix_sub.atoms), key=lambda pair: pair[0]) ] length = len(sub.atoms) #rank_time = 0 #frag_time = 0 for model in models: if isinstance(model, AtomicStructure): for atom in model.atoms: #session.logger.info("checking groups on %s" % atom.atomspec) for bonded_atom in atom.neighbors: if bonded_atom.element.name != sub.atoms[0].element: continue #session.logger.info("fragment for %s" % bonded_atom.atomspec) #frag_start = perf_counter() frag = get_fragment(bonded_atom, atom, length) #frag_stop = perf_counter() #frag_time += frag_stop - frag_start if frag.intersects(atoms): continue frag = frag.subtract(Atoms([atom])) if len(frag) != length: continue elements = sorted(frag.elements.names) if sub_elements != elements: #session.logger.info("wrong elements") continue #rank_start = perf_counter() frag_ranks = canonical_rank(frag) #rank_stop = perf_counter() #rank_time += rank_stop - rank_start #session.logger.warning(", ".join(sub_elements)) #session.logger.warning("%s;\n%s" % (", ".join(str(x) for x in sorted(frag_ranks)), ", ".join(str(x) for x in sorted(sub_ranks)))) sorted_frag_atoms = [ x for _, x in sorted(zip(frag_ranks, frag.instances()), key=lambda pair: pair[0]) ] #session.logger.warning("%s;\n%s" % (", ".join(x.atomspec for x in sorted_frag_atoms), ", ".join(x.name for x in sorted_sub_atoms))) for a, b in zip(sorted_frag_atoms, sorted_sub_atoms): #session.logger.info("%s %s" % (a.atomspec, b.name)) if a.element.name != b.element.name: #session.logger.info("different elements") break #session.logger.info("bonded: %s; other: %s" % (bonded_atom.atomspec, atom.atomspec)) if a is not bonded_atom and len(a.neighbors) != len( b.neighbors): #session.logger.info("different num neighbors") #session.logger.info("%s and %s" % (a.atomspec, b.name)) #session.logger.info("%i vs %i" % (len(a.neighbors), len(b.neighbors))) break elif a is bonded_atom and (len(a.neighbors) - 1) != len(b.neighbors): #session.logger.info("first atom, different num neighbors") #session.logger.info("%s and %s" % (a.atomspec, b.name)) #session.logger.info("%i vs %i" % (len(a.neighbors) - 1, len(b.neighbors))) break failed = False for i, j, k in zip( sorted([ aa.element.name for aa in a.neighbors if ((aa is not atom and a is bonded_atom) or a is not bonded_atom) ]), sorted([bb.element.name for bb in b.neighbors]), sorted([ aa for aa in a.neighbors if ((aa is not atom and a is bonded_atom) or a is not bonded_atom) ]), ): if i != j: #session.logger.info("failed %s %s, %s" % (i, j, k.atomspec)) failed = True break if failed: break else: atoms = atoms.merge(frag) #session.logger.info("spent %f time fragmenting" % frag_time) #session.logger.info("spent %f time ranking atoms" % rank_time) results.add_atoms(atoms)
def test_copy(self): sub = Substituent("COCH3") sub = sub.copy(name="COCH3") self.is_COCH3(sub) return
def main(argv): sterimol_parser = argparse.ArgumentParser( description= "calculate Boltzmann-weighted Sterimol parameters - see doi 10.1021/acscatal.8b04043", formatter_class=argparse.RawTextHelpFormatter) sterimol_parser.add_argument("infiles", metavar="input files", type=str, nargs="+", help="file containing coordinates and energy") sterimol_parser.add_argument("-if", "--input-format", type=str, default=None, choices=["log", "out", "dat"], dest="input_format", help="file format of input") sterimol_parser.add_argument( "-s", "--substituent-atom", type=str, required=True, dest="targets", help="substituent atom\n" + "1-indexed position of the starting position of the\n" + "substituent of which you are calculating sterimol\nparameters") sterimol_parser.add_argument( "-a", "--attached-to", type=str, required=True, dest="avoid", help="non-substituent atom\n" + "1-indexed position of the starting position of the atom\n" + "connected to the substituent of which you are calculating\n" + "sterimol parameters") sterimol_parser.add_argument( "-r", "--radii", type=str, default="bondi", choices=["bondi", "umn"], dest="radii", help="VDW radii to use in calculation\n" + "umn: main group vdw radii from J. Phys. Chem. A 2009, 113, 19, 5806–5812\n" + " (DOI: 10.1021/jp8111556)\n" + " transition metals are crystal radii from Batsanov, S.S. Van der Waals\n" + " Radii of Elements. Inorganic Materials 37, 871–885 (2001).\n" + " (DOI: 10.1023/A:1011625728803)\n" + "bondi: radii from J. Phys. Chem. 1964, 68, 3, 441–451 (DOI: 10.1021/j100785a001)\n" + "Default: bondi") sterimol_parser.add_argument( "-l", "--old-l", action="store_true", required=False, dest="old_L", help="approximate FORTRAN Sterimol method for determining L\n" "This is 0.4 + the ideal bond length for a target-H bond\n" "to outer VDW radii of atoms projected onto L-axis\n" "Default: L value is from VDW radii of target atom to outer\n" "VDW radii of atoms projected onto L-axis") sterimol_parser.add_argument("-t", "--temperature", type=float, default=298.15, required=False, dest="temperature", help="temperature in K\nDefault: 298.15") sterimol_parser.add_argument( "-f", "--frequency", action="store_true", default=False, required=False, dest="frequency", help="input files are frequency job output files\n" "additional average values will be calculated for ZPE, H, G, etc.") sterimol_parser.add_argument( "-w0", "--frequency-cutoff", type=float, default=100.0, required=False, dest="w0", help="cutoff frequency for quasi free energy corrections (1/cm)\n" + "Default: 100 cm^-1") sterimol_parser.add_argument("-v", "--verbose", action="store_true", default=False, required=False, dest="verbose", help="also print population") sterimol_parser.add_argument("-o", "--output", type=str, default=False, required=False, metavar="output destination", dest="outfile", help="output destination\n" + "Default: stdout") args = sterimol_parser.parse_args(args=argv) subs = [] energies = {"E": []} if args.frequency: energies["E+ZPE"] = [] energies["H(RRHO)"] = [] energies["G(RRHO)"] = [] energies["G(Quasi-RRHO)"] = [] energies["G(Quasi-Harmonic)"] = [] for infile in glob_files(args.infiles, parser=sterimol_parser): if args.input_format is not None: fr = FileReader((infile, args.input_format, infile), just_geom=False) else: fr = FileReader(infile, just_geom=False) geom = Geometry(fr) target = args.targets avoid = args.avoid end = geom.find(avoid)[0] frag = geom.get_fragment(target, stop=end) sub = Substituent(frag, end=end, detect=False) subs.append(sub) nrg = fr.other["energy"] energies["E"].append(nrg) if args.frequency: co = CompOutput(fr) dE, dH, entropy = co.therm_corr(temperature=args.temperature) rrho_dG = co.calc_G_corr(v0=0, temperature=args.temperature, method="RRHO") qrrho_dG = co.calc_G_corr(v0=args.w0, temperature=args.temperature, method="QRRHO") qharm_dG = co.calc_G_corr(v0=args.w0, temperature=args.temperature, method="QHARM") energies["E+ZPE"].append(nrg + co.ZPVE) energies["H(RRHO)"].append(nrg + dH) energies["G(RRHO)"].append(nrg + rrho_dG) energies["G(Quasi-RRHO)"].append(nrg + qrrho_dG) energies["G(Quasi-Harmonic)"].append(nrg + qharm_dG) s = "" for nrg_type in energies: energies_arr = np.array(energies[nrg_type]) energies_arr *= UNIT.HART_TO_KCAL if args.verbose and nrg_type == "E": s += "\t".join(["B1", "B2", "B3", "B4", "B5", "L", "file"]) s += "\n" for f, sub in zip(args.infiles, subs): data = sub.sterimol( radii=args.radii, old_L=args.old_L, ) s += "\t".join([ "%.2f" % data[x] for x in ["B1", "B2", "B3", "B4", "B5", "L"] ]) s += "\t%s\n" % f s += "weighted using %s:\n" % nrg_type data = Substituent.weighted_sterimol( subs, energies_arr, args.temperature, radii=args.radii, old_L=args.old_L, ) if args.verbose: coeff = boltzmann_coefficients(energies_arr, args.temperature) coeff /= sum(coeff) coeff *= 100 for f, c, e in zip(args.infiles, coeff, energies_arr): s += "%s %.1f%% (%.1f kcal/mol)\n" % (f, c, e - min(energies_arr)) s += "\t".join(["B1", "B2", "B3", "B4", "B5", "L"]) s += "\n" s += "\t".join( ["%.2f" % data[x] for x in ["B1", "B2", "B3", "B4", "B5", "L"]]) s += "\n" s += "\n" if not args.outfile: print(s) else: with open(args.outfile, "w") as f: f.write(s)
"when no input file is given, stdin is read and a format must be specified" ) geom = Geometry(f) target_list = [] for sub in args.substitutions: ndx_targets = sub.split('=')[0] target_list.append(geom.find(ndx_targets)) for i, sub in enumerate(args.substitutions): ndx_target = target_list[i] sub_name = '='.join(sub.split('=')[1:]) for target in ndx_target: if args.form[0] == 'from_library': sub = Substituent(sub_name) elif args.form[0] in ['iupac', 'smiles']: sub = Substituent.from_string(sub_name, args.form[0]) #replace old substituent with new substituent geom.substitute(sub, target) geom.refresh_connected() if args.outfile: FileWriter.write_xyz(geom, append=False, outfile=args.outfile[0]) else: s = FileWriter.write_xyz(geom, append=False, outfile=False) print(s)
type=str, default=None, required=False, metavar="output destination", dest="outfile", help="output destination\n" + "$i in the filename will be replaced with conformer number\n" + "if a directory is given, default is \"conformer-$i.xyz\" in that directory\n" + "$INFILE will be replaced with the name of the input file\n" + "Default: stdout") args = makeconf_parser.parse_args() if args.list_avail: s = "" for i, name in enumerate(sorted(Substituent.list())): sub = Substituent(name) if sub.conf_num > 1: s += "%-20s" % name # if (i + 1) % 3 == 0: if (i + 1) % 1 == 0: s += "\n" print(s.strip()) sys.exit(0) detect_subs = False s = "" skipped = 0
def test_substituent(self): sub = Substituent("COCH3") self.json_tester(sub, self.sub_equal)