Skip to content

Commit ff50f17

Browse files
authored
expose additional SubstanceGroup data members to Python (rdkit#3375)
* support read-only access to cstates from python * expose GetBrackets * expose getAttachPoints too remove vestigial SubstanceGroupCState_VECT
1 parent 8bcb625 commit ff50f17

2 files changed

Lines changed: 225 additions & 22 deletions

File tree

Code/GraphMol/Wrap/SubstanceGroup.cpp

Lines changed: 54 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -65,25 +65,53 @@ void addBracketHelper(SubstanceGroup &self, python::object pts) {
6565
self.addBracket(bkt);
6666
}
6767

68+
python::tuple getCStatesHelper(const SubstanceGroup &self) {
69+
python::list res;
70+
for (const auto cs : self.getCStates()) {
71+
res.append(cs);
72+
}
73+
return python::tuple(res);
74+
}
75+
76+
python::tuple getBracketsHelper(const SubstanceGroup &self) {
77+
python::list res;
78+
for (const auto brk : self.getBrackets()) {
79+
res.append(python::make_tuple(brk[0], brk[1], brk[2]));
80+
}
81+
return python::tuple(res);
82+
}
83+
84+
python::tuple getAttachPointsHelper(const SubstanceGroup &self) {
85+
python::list res;
86+
for (const auto ap : self.getAttachPoints()) {
87+
res.append(ap);
88+
}
89+
return python::tuple(res);
90+
}
6891
} // namespace
6992

7093
std::string sGroupClassDoc =
7194
"A collection of atoms and bonds with associated properties\n";
7295

7396
struct sgroup_wrap {
7497
static void wrap() {
75-
// register the vector_indexing_suite for SubstanceGroups
76-
// if it hasn't already been done.
77-
// logic from https://stackoverflow.com/a/13017303
78-
boost::python::type_info info =
79-
boost::python::type_id<std::vector<RDKit::SubstanceGroup>>();
80-
const boost::python::converter::registration *reg =
81-
boost::python::converter::registry::query(info);
82-
if (reg == nullptr || (*reg).m_to_python == nullptr) {
83-
python::class_<std::vector<RDKit::SubstanceGroup>>("SubstanceGroup_VECT")
84-
.def(python::vector_indexing_suite<
85-
std::vector<RDKit::SubstanceGroup>>());
86-
}
98+
RegisterVectorConverter<SubstanceGroup>("SubstanceGroup_VECT");
99+
100+
python::class_<SubstanceGroup::CState,
101+
boost::shared_ptr<SubstanceGroup::CState>>(
102+
"SubstanceGroupCState", "CSTATE for a SubstanceGroup", python::init<>())
103+
.def_readonly("bondIdx", &SubstanceGroup::CState::bondIdx)
104+
.def_readonly("vector", &SubstanceGroup::CState::vector);
105+
106+
python::class_<SubstanceGroup::AttachPoint,
107+
boost::shared_ptr<SubstanceGroup::AttachPoint>>(
108+
"SubstanceGroupAttach", "AttachPoint for a SubstanceGroup",
109+
python::init<>())
110+
.def_readonly("aIdx", &SubstanceGroup::AttachPoint::aIdx,
111+
"attachment index")
112+
.def_readonly("lvIdx", &SubstanceGroup::AttachPoint::lvIdx,
113+
"leaving atom or index (0 for implied)")
114+
.def_readonly("id", &SubstanceGroup::AttachPoint::id, "attachment id");
87115

88116
python::class_<SubstanceGroup, boost::shared_ptr<SubstanceGroup>>(
89117
"SubstanceGroup", sGroupClassDoc.c_str(), python::no_init)
@@ -93,28 +121,31 @@ struct sgroup_wrap {
93121
.def("GetIndexInMol", &SubstanceGroup::getIndexInMol,
94122
"returns the index of this SubstanceGroup in the owning "
95123
"molecule's list.")
96-
.def(
97-
"GetAtoms", &SubstanceGroup::getAtoms,
98-
"returns a list of the indices of the atoms in this SubstanceGroup",
99-
python::return_value_policy<python::copy_const_reference>())
124+
.def("GetAtoms", &SubstanceGroup::getAtoms,
125+
"returns a list of the indices of the atoms in this "
126+
"SubstanceGroup",
127+
python::return_value_policy<python::copy_const_reference>())
100128
.def("GetParentAtoms", &SubstanceGroup::getParentAtoms,
101129
"returns a list of the indices of the parent atoms in this "
102130
"SubstanceGroup",
103131
python::return_value_policy<python::copy_const_reference>())
104-
.def(
105-
"GetBonds", &SubstanceGroup::getBonds,
106-
"returns a list of the indices of the bonds in this SubstanceGroup",
107-
python::return_value_policy<python::copy_const_reference>())
132+
.def("GetBonds", &SubstanceGroup::getBonds,
133+
"returns a list of the indices of the bonds in this "
134+
"SubstanceGroup",
135+
python::return_value_policy<python::copy_const_reference>())
108136
.def("AddAtomWithIdx", &SubstanceGroup::addAtomWithIdx)
109137
.def("AddBondWithIdx", &SubstanceGroup::addBondWithIdx)
110138
.def("AddParentAtomWithIdx", &SubstanceGroup::addParentAtomWithIdx)
111139
.def("AddAtomWithBookmark", &SubstanceGroup::addAtomWithBookmark)
112140
.def("AddParentAtomWithBookmark",
113141
&SubstanceGroup::addParentAtomWithBookmark)
114142
.def("AddCState", &SubstanceGroup::addCState)
143+
.def("GetCStates", getCStatesHelper)
115144
.def("AddBondWithBookmark", &SubstanceGroup::addBondWithBookmark)
116145
.def("AddAttachPoint", &SubstanceGroup::addAttachPoint)
146+
.def("GetAttachPoints", getAttachPointsHelper)
117147
.def("AddBracket", addBracketHelper)
148+
.def("GetBrackets", getBracketsHelper)
118149

119150
.def("SetProp",
120151
(void (RDProps::*)(const std::string &, std::string, bool) const) &
@@ -183,13 +214,15 @@ struct sgroup_wrap {
183214
.def("GetPropNames", &SubstanceGroup::getPropList,
184215
(python::arg("self"), python::arg("includePrivate") = false,
185216
python::arg("includeComputed") = false),
186-
"Returns a list of the properties set on the SubstanceGroup.\n\n")
217+
"Returns a list of the properties set on the "
218+
"SubstanceGroup.\n\n")
187219
.def("GetPropsAsDict", GetPropsAsDict<SubstanceGroup>,
188220
(python::arg("self"), python::arg("includePrivate") = true,
189221
python::arg("includeComputed") = true),
190222
"Returns a dictionary of the properties set on the "
191223
"SubstanceGroup.\n"
192224
" n.b. some properties cannot be converted to python types.\n");
225+
193226
python::def("GetMolSubstanceGroups", &getMolSubstanceGroups,
194227
"returns a copy of the molecule's SubstanceGroups (if any)",
195228
python::with_custodian_and_ward_postcall<0, 1>());

Code/GraphMol/Wrap/testSGroups.py

Lines changed: 171 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -287,9 +287,179 @@ def testCopying(self):
287287
newsg = Chem.AddMolSubstanceGroup(mol2, sgs[1])
288288
newsg.SetProp("FIELDNAME", "blah_data")
289289
molb = Chem.MolToV3KMolBlock(mol2)
290-
print(molb)
291290
self.assertGreater(molb.find("M V30 3 DAT 0 ATOMS=(1 6) FIELDNAME=blah_data"), 0)
292291

292+
def testCStates(self):
293+
mol = Chem.MolFromMolBlock('''
294+
ACCLDraw08282007542D
295+
296+
0 0 0 0 0 999 V3000
297+
M V30 BEGIN CTAB
298+
M V30 COUNTS 9 9 1 0 0
299+
M V30 BEGIN ATOM
300+
M V30 1 C 8.759 -6.2839 0 0
301+
M V30 2 C 10.8013 -6.2833 0 0
302+
M V30 3 C 9.7821 -5.6936 0 0
303+
M V30 4 C 10.8013 -7.4648 0 0
304+
M V30 5 C 8.759 -7.4701 0 0
305+
M V30 6 C 9.7847 -8.0543 0 0
306+
M V30 7 C 11.8245 -5.6926 0 0
307+
M V30 8 O 12.8482 -6.2834 0 0
308+
M V30 9 O 11.8245 -4.5104 0 0
309+
M V30 END ATOM
310+
M V30 BEGIN BOND
311+
M V30 1 1 6 4
312+
M V30 2 2 5 6
313+
M V30 3 1 2 3
314+
M V30 4 1 1 5
315+
M V30 5 2 4 2
316+
M V30 6 2 3 1
317+
M V30 7 2 7 9
318+
M V30 8 1 7 8
319+
M V30 9 1 2 7
320+
M V30 END BOND
321+
M V30 BEGIN SGROUP
322+
M V30 1 SUP 1 ATOMS=(3 7 8 9) XBONDS=(1 9) CSTATE=(4 9 -1.02 -0.59 0) LABEL=-
323+
M V30 CO2H
324+
M V30 END SGROUP
325+
M V30 END CTAB
326+
M END
327+
''')
328+
sgs = Chem.GetMolSubstanceGroups(mol)
329+
self.assertEqual(len(sgs), 1)
330+
sg0 = sgs[0]
331+
pd = sg0.GetPropsAsDict()
332+
self.assertTrue('TYPE' in pd)
333+
self.assertEqual(pd['TYPE'], 'SUP')
334+
cstates = sg0.GetCStates()
335+
self.assertEqual(len(cstates), 1)
336+
cs0 = cstates[0]
337+
self.assertEqual(cs0.bondIdx, 8)
338+
self.assertAlmostEqual(cs0.vector.x, -1.02, 2)
339+
self.assertAlmostEqual(cs0.vector.y, -0.59, 2)
340+
341+
def testBrackets(self):
342+
mol = Chem.MolFromMolBlock('''
343+
Mrv2014 08282011142D
344+
345+
0 0 0 0 0 999 V3000
346+
M V30 BEGIN CTAB
347+
M V30 COUNTS 6 5 1 0 0
348+
M V30 BEGIN ATOM
349+
M V30 1 C -15.7083 2 0 0
350+
M V30 2 C -14.3747 2.77 0 0
351+
M V30 3 O -13.041 2 0 0
352+
M V30 4 C -11.7073 2.77 0 0
353+
M V30 5 C -10.3736 2 0 0
354+
M V30 6 C -9.0399 2.77 0 0
355+
M V30 END ATOM
356+
M V30 BEGIN BOND
357+
M V30 1 1 1 2
358+
M V30 2 1 2 3
359+
M V30 3 1 3 4
360+
M V30 4 1 4 5
361+
M V30 5 1 5 6
362+
M V30 END BOND
363+
M V30 BEGIN SGROUP
364+
M V30 1 MON 0 ATOMS=(2 3 4) BRKXYZ=(9 -13.811 1.23 0 -13.811 3.54 0 0 0 0) -
365+
M V30 BRKXYZ=(9 -10.9373 3.54 0 -10.9373 1.23 0 0 0 0)
366+
M V30 END SGROUP
367+
M V30 END CTAB
368+
M END''')
369+
sgs = Chem.GetMolSubstanceGroups(mol)
370+
self.assertEqual(len(sgs), 1)
371+
sg0 = sgs[0]
372+
pd = sg0.GetPropsAsDict()
373+
self.assertTrue('TYPE' in pd)
374+
self.assertEqual(pd['TYPE'], 'MON')
375+
brackets = sg0.GetBrackets()
376+
self.assertEqual(len(brackets), 2)
377+
b = brackets[0]
378+
self.assertEqual(len(b), 3)
379+
self.assertAlmostEqual(b[0].x, -13.811, 3)
380+
self.assertAlmostEqual(b[0].y, 1.230, 3)
381+
self.assertAlmostEqual(b[1].x, -13.811, 3)
382+
self.assertAlmostEqual(b[1].y, 3.540, 3)
383+
self.assertAlmostEqual(b[2].x, 0, 3)
384+
self.assertAlmostEqual(b[2].y, 0, 3)
385+
386+
b = brackets[1]
387+
self.assertEqual(len(b), 3)
388+
self.assertAlmostEqual(b[0].x, -10.937, 3)
389+
self.assertAlmostEqual(b[0].y, 3.54, 3)
390+
self.assertAlmostEqual(b[1].x, -10.937, 3)
391+
self.assertAlmostEqual(b[1].y, 1.23, 3)
392+
self.assertAlmostEqual(b[2].x, 0, 3)
393+
self.assertAlmostEqual(b[2].y, 0, 3)
394+
395+
def testAttachPoints(self):
396+
mol = Chem.MolFromMolBlock('''
397+
Mrv2014 09012006262D
398+
399+
0 0 0 0 0 999 V3000
400+
M V30 BEGIN CTAB
401+
M V30 COUNTS 4 3 1 0 0
402+
M V30 BEGIN ATOM
403+
M V30 1 C -5.0833 0.0833 0 0
404+
M V30 2 C -3.7497 0.8533 0 0
405+
M V30 3 O -2.416 0.0833 0 0
406+
M V30 4 O -3.7497 2.3933 0 0
407+
M V30 END ATOM
408+
M V30 BEGIN BOND
409+
M V30 1 1 1 2
410+
M V30 2 1 2 3
411+
M V30 3 2 2 4
412+
M V30 END BOND
413+
M V30 BEGIN SGROUP
414+
M V30 1 SUP 0 ATOMS=(3 2 3 4) SAP=(3 2 1 1) XBONDS=(1 1) LABEL=CO2H ESTATE=E
415+
M V30 END SGROUP
416+
M V30 END CTAB
417+
M END''')
418+
sgs = Chem.GetMolSubstanceGroups(mol)
419+
self.assertEqual(len(sgs), 1)
420+
sg0 = sgs[0]
421+
pd = sg0.GetPropsAsDict()
422+
self.assertTrue('TYPE' in pd)
423+
self.assertEqual(pd['TYPE'], 'SUP')
424+
aps = sg0.GetAttachPoints()
425+
self.assertEqual(len(aps), 1)
426+
self.assertEqual(aps[0].aIdx, 1)
427+
self.assertEqual(aps[0].lvIdx, 0)
428+
self.assertEqual(aps[0].id, '1')
429+
430+
mol = Chem.MolFromMolBlock('''
431+
Mrv2014 09012006262D
432+
433+
0 0 0 0 0 999 V3000
434+
M V30 BEGIN CTAB
435+
M V30 COUNTS 4 3 0 0 0
436+
M V30 BEGIN ATOM
437+
M V30 1 C -5.0833 0.0833 0 0
438+
M V30 2 C -3.7497 0.8533 0 0
439+
M V30 3 O -2.416 0.0833 0 0
440+
M V30 4 O -3.7497 2.3933 0 0
441+
M V30 END ATOM
442+
M V30 BEGIN BOND
443+
M V30 1 1 1 2
444+
M V30 2 1 2 3
445+
M V30 3 2 2 4
446+
M V30 END BOND
447+
M V30 END CTAB
448+
M END''')
449+
sgs = Chem.GetMolSubstanceGroups(mol)
450+
self.assertEqual(len(sgs), 0)
451+
452+
sg = Chem.CreateMolSubstanceGroup(mol, "SUP")
453+
sg.AddAtomWithIdx(1)
454+
sg.AddAtomWithIdx(2)
455+
sg.AddAtomWithIdx(3)
456+
sg.AddBondWithIdx(0)
457+
sg.SetProp('LABEL', 'CO2H')
458+
sg.AddAttachPoint(1, 0, '1')
459+
molb = Chem.MolToV3KMolBlock(mol)
460+
self.assertGreater(
461+
molb.find('M V30 1 SUP 0 ATOMS=(3 2 3 4) XBONDS=(1 1) LABEL=CO2H SAP=(3 2 1 1)'), 0)
462+
293463

294464
if __name__ == '__main__':
295465
print("Testing SubstanceGroups wrapper")

0 commit comments

Comments
 (0)