Partial fix to a problem with implicit Hs being written to SMARTS (#8893)

* Partially resolve a problem with implicit Hs ending up in SMARTS

* update doctest

* document in release notes
This commit is contained in:
Greg Landrum
2025-10-24 07:50:06 +02:00
committed by GitHub
parent 8844c55572
commit 0ee90279b5
10 changed files with 108 additions and 64 deletions

View File

@@ -1089,10 +1089,12 @@ TEST_CASE("CXSMILES for reactions", "[cxsmiles]") {
common_properties::atomLabel, alabel));
CHECK(alabel == "_AP1");
auto expected_cxsmiles = "[CH3:1][CH:2]([CH3:3])[*:4].[OH:5][CH2:6][*:7]>>[CH3:1][CH:2]([CH3:3])[CH2:6][OH:5] |$;;;_AP1;;;_AP1;;;;;$|";
auto expected_cxsmiles =
"[CH3:1][CH:2]([CH3:3])[*:4].[OH:5][CH2:6][*:7]>>[CH3:1][CH:2]([CH3:3])[CH2:6][OH:5] |$;;;_AP1;;;_AP1;;;;;$|";
SmilesWriteParams params;
auto flags = RDKit::SmilesWrite::CX_ALL ^ RDKit::SmilesWrite::CX_ATOM_PROPS;
std::string output_cxsmiles = ChemicalReactionToCXRxnSmiles(*rxn, params, flags);
std::string output_cxsmiles =
ChemicalReactionToCXRxnSmiles(*rxn, params, flags);
CHECK(output_cxsmiles == expected_cxsmiles);
auto roundtrip = v2::ReactionParser::ReactionFromSmiles(output_cxsmiles);
@@ -1121,13 +1123,14 @@ TEST_CASE("CXSMILES for reactions", "[cxsmiles]") {
common_properties::atomLabel, alabel));
CHECK(alabel == "_AP1");
std::string expected_cxsmarts = "[C&H3:1][C&H1:2]([C&H3:3])[*:4].[O&H1:5][C&H2:6][*:7]>O=C=O>[C&H3:1][C&H1:2]([C&H3:3])[C&H2:6][O&H1:5] |$;;;_AP1;;;_AP1;;;;;;;;$|";
std::string expected_cxsmarts =
"[C&H3:1][C&H1:2]([C&H3:3])[*:4].[O&H1:5][C&H2:6][*:7]>O=C=O>[C&H3:1][C&H1:2]([C&H3:3])[C&H2:6][O&H1:5] |$;;;_AP1;;;_AP1;;;;;;;;$|";
SmilesWriteParams params;
auto flags = RDKit::SmilesWrite::CX_ALL ^ RDKit::SmilesWrite::CX_ATOM_PROPS;
std::string output_cxsmarts = ChemicalReactionToCXRxnSmarts(*rxn, params, flags);
std::string output_cxsmarts =
ChemicalReactionToCXRxnSmarts(*rxn, params, flags);
auto roundtrip = v2::ReactionParser::ReactionFromSmarts(output_cxsmarts);
REQUIRE(roundtrip);
CHECK(roundtrip->getReactants().size() == 2);
CHECK(roundtrip->getReactants()[0]->getAtomWithIdx(3)->getPropIfPresent(
@@ -1138,7 +1141,6 @@ TEST_CASE("CXSMILES for reactions", "[cxsmiles]") {
CHECK(alabel == "_AP1");
CHECK(output_cxsmarts == expected_cxsmarts);
}
SECTION("missing products") {
@@ -1155,7 +1157,8 @@ TEST_CASE("CXSMILES for reactions", "[cxsmiles]") {
common_properties::atomLabel, alabel));
CHECK(alabel == "_AP1");
auto roundtrip = v2::ReactionParser::ReactionFromSmarts(ChemicalReactionToCXRxnSmarts(*rxn));
auto roundtrip = v2::ReactionParser::ReactionFromSmarts(
ChemicalReactionToCXRxnSmarts(*rxn));
REQUIRE(roundtrip);
CHECK(roundtrip->getReactants().size() == 2);
CHECK(roundtrip->getReactants()[0]->getAtomWithIdx(3)->getPropIfPresent(
@@ -1165,10 +1168,12 @@ TEST_CASE("CXSMILES for reactions", "[cxsmiles]") {
common_properties::atomLabel, alabel));
CHECK(alabel == "_AP1");
std::string expected_cxsmarts = "[C&H3:1][C&H1:2]([C&H3:3])[*:4].[O&H1:5][C&H2:6][*:7]>> |$;;;_AP1;;;_AP1$|";
std::string expected_cxsmarts =
"[C&H3:1][C&H1:2]([C&H3:3])[*:4].[O&H1:5][C&H2:6][*:7]>> |$;;;_AP1;;;_AP1$|";
SmilesWriteParams params;
auto flags = RDKit::SmilesWrite::CX_ALL ^ RDKit::SmilesWrite::CX_ATOM_PROPS;
std::string output_cxsmarts = ChemicalReactionToCXRxnSmarts(*rxn, params, flags);
std::string output_cxsmarts =
ChemicalReactionToCXRxnSmarts(*rxn, params, flags);
CHECK(output_cxsmarts == expected_cxsmarts);
}
@@ -1202,7 +1207,8 @@ TEST_CASE("CXSMILES for reactions", "[cxsmiles]") {
SmilesWriteParams params;
auto flags = RDKit::SmilesWrite::CX_ALL ^ RDKit::SmilesWrite::CX_ATOM_PROPS;
auto output_cxsmarts = ChemicalReactionToCXRxnSmarts(*rxn, params, flags);
auto expected_cxsmarts = "[#6H3:1]-[#6H:2](-[#6H3:3])-[#0:4].[Fe:8]<-[#8H:5]-[#6H2:6]-[#0:7]>>[Fe:8]<-[#8H:5]-[#6H2:6]-[#6H2:1]-[#6H:2](-[#6H3:3])-[#0:4] |$;;;_AP1;;;;_AP1;;;;;;;_AP1$,C:5.3,9.6,SgD:6:foo:bar::::,SgD:10:bar:baz::::|";
auto expected_cxsmarts =
"[#6:1]-[#6:2](-[#6:3])-[#0:4].[Fe:8]<-[#8:5]-[#6:6]-[#0:7]>>[Fe:8]<-[#8:5]-[#6:6]-[#6:1]-[#6:2](-[#6:3])-[#0:4] |$;;;_AP1;;;;_AP1;;;;;;;_AP1$,C:5.3,9.6,SgD:6:foo:bar::::,SgD:10:bar:baz::::|";
CHECK(output_cxsmarts == expected_cxsmarts);
}
@@ -1213,10 +1219,12 @@ TEST_CASE("CXSMILES for reactions", "[cxsmiles]") {
// clang-format on
REQUIRE(rxn);
std::string expected_cxsmarts = "[#6H3:6]-[#8:5]-[#6H:3](-*)-[#8:2]-*>>[#6H3:6]-[#7H:5]-[#6H:3](-*)-[#8:2]-* |$;;;star_e;;star_e;;;;star_e;;star_e$,SgD:1,0:foo:bar::::,,SgD:7,6:foo:baz::::,,,Sg:n:4,2,1,0::ht:::,,Sg:n:10,8,7,6::ht:::,SgH:3:1.1|";
std::string expected_cxsmarts =
"[#6:6]-[#8:5]-[#6:3](-*)-[#8:2]-*>>[#6:6]-[#7:5]-[#6:3](-*)-[#8:2]-* |$;;;star_e;;star_e;;;;star_e;;star_e$,SgD:1,0:foo:bar::::,,SgD:7,6:foo:baz::::,,,Sg:n:4,2,1,0::ht:::,,Sg:n:10,8,7,6::ht:::,SgH:3:1.1|";
SmilesWriteParams params;
auto flags = RDKit::SmilesWrite::CX_ALL ^ RDKit::SmilesWrite::CX_ATOM_PROPS;
std::string output_cxsmarts = ChemicalReactionToCXRxnSmarts(*rxn, params, flags);
std::string output_cxsmarts =
ChemicalReactionToCXRxnSmarts(*rxn, params, flags);
CHECK(output_cxsmarts == expected_cxsmarts);
// Test properties of the rxn itself.
@@ -1240,7 +1248,8 @@ TEST_CASE("CXSMILES for reactions", "[cxsmiles]") {
auto roundtrip = v2::ReactionParser::ReactionFromSmarts(output_cxsmarts);
REQUIRE(roundtrip);
const auto &sgsRoundReact = getSubstanceGroups(*roundtrip->getReactants()[0]);
const auto &sgsRoundReact =
getSubstanceGroups(*roundtrip->getReactants()[0]);
REQUIRE(&sgsRoundReact);
const auto &sgsRoundProd = getSubstanceGroups(*roundtrip->getProducts()[0]);
REQUIRE(&sgsRoundProd);
@@ -1255,8 +1264,9 @@ TEST_CASE("CXSMILES for reactions", "[cxsmiles]") {
// Check that the properties are set on the roundtrip rxn.
CHECK(sgsRoundProd[0].getProp<unsigned int>("PARENT") == 2);
// Doesn't work because .... maybe because the substance groups are intertwined react/prod? The sg hierarchy is not being written for the reactant...
// CHECK(sgsRoundReact[0].getProp<unsigned int>("PARENT") == 2);
// Doesn't work because .... maybe because the substance groups are
// intertwined react/prod? The sg hierarchy is not being written for the
// reactant... CHECK(sgsRoundReact[0].getProp<unsigned int>("PARENT") == 2);
}
SECTION("link nodes") {
// clang-format off
@@ -1314,7 +1324,8 @@ TEST_CASE("CXSMILES for reactions", "[cxsmiles]") {
CHECK(rxn->getProducts()[0]->getBondWithIdx(1)->getStereo() ==
Bond::BondStereo::STEREOCIS);
auto roundtrip = v2::ReactionParser::ReactionFromSmarts(ChemicalReactionToCXRxnSmarts(*rxn));
auto roundtrip = v2::ReactionParser::ReactionFromSmarts(
ChemicalReactionToCXRxnSmarts(*rxn));
REQUIRE(roundtrip);
CHECK(roundtrip->getReactants().size() == 1);
CHECK(roundtrip->getProducts().size() == 1);
@@ -1336,7 +1347,8 @@ TEST_CASE("CXSMILES for reactions", "[cxsmiles]") {
"_MolFileBondCfg", bondcfg));
CHECK(bondcfg == 2);
auto roundtrip = v2::ReactionParser::ReactionFromSmarts(ChemicalReactionToCXRxnSmarts(*rxn));
auto roundtrip = v2::ReactionParser::ReactionFromSmarts(
ChemicalReactionToCXRxnSmarts(*rxn));
REQUIRE(roundtrip);
CHECK(roundtrip->getReactants().size() == 1);
CHECK(roundtrip->getProducts().size() == 1);
@@ -1351,7 +1363,8 @@ TEST_CASE("CXSMILES for reactions", "[cxsmiles]") {
SmilesWriteParams params;
auto flags = RDKit::SmilesWrite::CX_ALL ^ RDKit::SmilesWrite::CX_ATOM_PROPS;
auto output_cxsmarts = ChemicalReactionToCXRxnSmarts(*rxn, params, flags);
auto expected_cxsmarts = "[#6]-[#6](-[#8])(-[#9])-[#17]>>[#6]-[#6](-[#7])(-[#9])-[#17] |w:1.0,6.5|";
auto expected_cxsmarts =
"[#6]-[#6](-[#8])(-[#9])-[#17]>>[#6]-[#6](-[#7])(-[#9])-[#17] |w:1.0,6.5|";
CHECK(output_cxsmarts == expected_cxsmarts);
}
}
@@ -2229,11 +2242,10 @@ TEST_CASE("Structural fingerprints values") {
}
}
TEST_CASE(
"Github #6015: react_idx property") {
TEST_CASE("Github #6015: react_idx property") {
SECTION("Ensure that atoms are marked with their reactant idx") {
std::unique_ptr<ChemicalReaction> rxn{
RxnSmartsToChemicalReaction("[C:1][O:2].[N:3][S:4]>>[C:1][O:2][N:3][S:4]C")};
std::unique_ptr<ChemicalReaction> rxn{RxnSmartsToChemicalReaction(
"[C:1][O:2].[N:3][S:4]>>[C:1][O:2][N:3][S:4]C")};
REQUIRE(rxn);
rxn->initReactantMatchers();
ROMOL_SPTR mol1(SmilesToMol("CO"));
@@ -2242,27 +2254,37 @@ TEST_CASE(
REQUIRE(reactants.size() == 2);
REQUIRE(reactants[0]);
REQUIRE(reactants[1]);
auto products = rxn->runReactants(reactants);
REQUIRE(products.size() == 1);
CHECK(products[0][0]->getAtomWithIdx(0)->getProp<unsigned int>("react_idx") == 0);
CHECK(products[0][0]->getAtomWithIdx(0)->getProp<unsigned int>("react_atom_idx") == 0);
CHECK(products[0][0]->getAtomWithIdx(1)->getProp<unsigned int>("react_idx") == 0);
CHECK(products[0][0]->getAtomWithIdx(1)->getProp<unsigned int>("react_atom_idx") == 1);
CHECK(products[0][0]->getAtomWithIdx(2)->getProp<unsigned int>("react_idx") == 1);
CHECK(products[0][0]->getAtomWithIdx(2)->getProp<unsigned int>("react_atom_idx") == 0);
CHECK(products[0][0]->getAtomWithIdx(3)->getProp<unsigned int>("react_idx") == 1);
CHECK(products[0][0]->getAtomWithIdx(3)->getProp<unsigned int>("react_atom_idx") == 1);
CHECK(products[0][0]->getAtomWithIdx(4)->hasProp("react_atom_idx") == false);
CHECK(products[0][0]->getAtomWithIdx(0)->getProp<unsigned int>(
"react_idx") == 0);
CHECK(products[0][0]->getAtomWithIdx(0)->getProp<unsigned int>(
"react_atom_idx") == 0);
CHECK(products[0][0]->getAtomWithIdx(1)->getProp<unsigned int>(
"react_idx") == 0);
CHECK(products[0][0]->getAtomWithIdx(1)->getProp<unsigned int>(
"react_atom_idx") == 1);
CHECK(products[0][0]->getAtomWithIdx(2)->getProp<unsigned int>(
"react_idx") == 1);
CHECK(products[0][0]->getAtomWithIdx(2)->getProp<unsigned int>(
"react_atom_idx") == 0);
CHECK(products[0][0]->getAtomWithIdx(3)->getProp<unsigned int>(
"react_idx") == 1);
CHECK(products[0][0]->getAtomWithIdx(3)->getProp<unsigned int>(
"react_atom_idx") == 1);
CHECK(products[0][0]->getAtomWithIdx(4)->hasProp("react_atom_idx") ==
false);
CHECK(products[0][0]->getAtomWithIdx(4)->hasProp("react_idx") == false);
CHECK(products[0][0]->getAtomWithIdx(5)->getProp<unsigned int>("react_idx") == 1);
CHECK(products[0][0]->getAtomWithIdx(5)->getProp<unsigned int>("react_atom_idx") == 2);
CHECK(products[0][0]->getAtomWithIdx(5)->getProp<unsigned int>(
"react_idx") == 1);
CHECK(products[0][0]->getAtomWithIdx(5)->getProp<unsigned int>(
"react_atom_idx") == 2);
}
}

View File

@@ -747,7 +747,7 @@ std::string getNonQueryAtomSmarts(const Atom *atom) {
} else {
res << atom->getSymbol();
}
bool addedChirality = false;
if (atom->hasOwningMol() &&
atom->getOwningMol().hasProp(common_properties::_doIsoSmiles)) {
if (atom->getChiralTag() != Atom::CHI_UNSPECIFIED &&
@@ -757,9 +757,11 @@ std::string getNonQueryAtomSmarts(const Atom *atom) {
switch (atom->getChiralTag()) {
case Atom::CHI_TETRAHEDRAL_CW:
res << "@@";
addedChirality = true;
break;
case Atom::CHI_TETRAHEDRAL_CCW:
res << "@";
addedChirality = true;
break;
default:
break;
@@ -767,13 +769,11 @@ std::string getNonQueryAtomSmarts(const Atom *atom) {
}
}
auto hs = atom->getNumExplicitHs();
// FIX: probably should be smarter about Hs:
if (hs) {
if (addedChirality && atom->getNumExplicitHs() == 1) {
// FIX: this isn't really correct in many cases, but
// fixing it requires opening a fairly large construction site on the
// SMARTS handling side. We'll do this later.
res << "H";
if (hs > 1) {
res << hs;
}
}
auto chg = atom->getFormalCharge();
if (chg) {

View File

@@ -2978,11 +2978,11 @@ TEST_CASE("Github #7372: SMILES output option to disable dative bonds") {
auto m = "[NH3]->[Fe]-[NH2]"_smiles;
REQUIRE(m);
auto smi = MolToSmarts(*m);
CHECK(smi == "[#7H3]->[Fe]-[#7H2]");
CHECK(smi == "[#7]->[Fe]-[#7]");
SmilesWriteParams ps;
ps.includeDativeBonds = false;
auto newSmi = MolToSmarts(*m, ps);
CHECK(newSmi == "[#7H3]-[Fe]-[#7H2]");
CHECK(newSmi == "[#7]-[Fe]-[#7]");
}
}

View File

@@ -1509,7 +1509,7 @@ TEST_CASE("Github #7372: SMILES output option to disable dative bonds") {
auto m = "[NH3]->[Fe]-[NH2]"_smiles;
REQUIRE(m);
auto smi = MolToCXSmarts(*m);
CHECK(smi == "[#7H3]-[Fe]-[#7H2] |C:0.0|");
CHECK(smi == "[#7]-[Fe]-[#7] |C:0.0|");
}
SECTION("two dative bonds") {
auto m = "[NH3][Fe][NH3]"_smiles; // auto single->dative conversion
@@ -1521,7 +1521,7 @@ TEST_CASE("Github #7372: SMILES output option to disable dative bonds") {
auto m = "[NH3][Fe][NH3]"_smiles; // auto single->dative conversion
REQUIRE(m);
auto smi = MolToCXSmarts(*m);
CHECK(smi == "[#7H3]-[Fe]-[#7H3] |C:0.0,2.1|");
CHECK(smi == "[#7]-[Fe]-[#7] |C:0.0,2.1|");
}
}

View File

@@ -29,7 +29,7 @@ TEST_CASE("Github #8424: direction on aromatic bonds in SMARTS") {
auto m = "C/N=c1/[nH]cc(Br)nc1"_smiles;
REQUIRE(m);
auto smarts = MolToSmarts(*m);
CHECK(smarts == "[#6]/[#7]=[#6]1/[#7H]:[#6]:[#6](-[#35]):[#7]:[#6]:1");
CHECK(smarts == "[#6]/[#7]=[#6]1/[#7]:[#6]:[#6](-[#35]):[#7]:[#6]:1");
}
SECTION("as reported") {
auto m =
@@ -65,4 +65,22 @@ TEST_CASE("repeated explicit H counts and charges") {
CHECK(m->getAtomWithIdx(0)->getFormalCharge() == 0);
}
}
}
TEST_CASE("implicit Hs from SMILES should not make it into SMARTS") {
SECTION("aromatic N") {
auto m = "c1ccc[nH]1"_smiles;
REQUIRE(m);
auto smarts = MolToSmarts(*m);
CHECK(smarts == "[#6]1:[#6]:[#6]:[#6]:[#7]:1");
}
SECTION("chirality") {
// as of this writing, we still keep Hs in the SMARTS for chiral centers.
// it's inconsistent to do so, but we have explicitly punted on fixing
// this for now.
auto m = "C[C@H](N)F"_smiles;
REQUIRE(m);
auto smarts = MolToSmarts(*m);
CHECK(smarts == "[#6]-[#6@H](-[#7])-[#9]");
}
}

View File

@@ -1021,14 +1021,14 @@ void testSmilesSmarts() {
mol = SmilesToMol(smi);
TEST_ASSERT(mol);
sma = MolToSmarts(*mol);
TEST_ASSERT(sma == "[#6H2-]-[#6]");
TEST_ASSERT(sma == "[#6-]-[#6]");
delete mol;
smi = "[CH-2]C";
mol = SmilesToMol(smi);
TEST_ASSERT(mol);
sma = MolToSmarts(*mol);
TEST_ASSERT(sma == "[#6H-2]-[#6]");
TEST_ASSERT(sma == "[#6-2]-[#6]");
delete mol;
smi = "[CH4+]C";
@@ -1037,14 +1037,14 @@ void testSmilesSmarts() {
mol = SmilesToMol(smi, debugParse, sanitize);
TEST_ASSERT(mol);
sma = MolToSmarts(*mol);
TEST_ASSERT(sma == "[#6H4+]-[#6]");
TEST_ASSERT(sma == "[#6+]-[#6]");
delete mol;
smi = "[CH5+2]C";
mol = SmilesToMol(smi, debugParse, sanitize);
TEST_ASSERT(mol);
sma = MolToSmarts(*mol);
TEST_ASSERT(sma == "[#6H5+2]-[#6]");
TEST_ASSERT(sma == "[#6+2]-[#6]");
delete mol;
smi = "c1ccccc1";
@@ -2646,6 +2646,7 @@ void testGithub2565() {
bool recursionPossible = true;
bool useChirality = true;
std::vector<MatchVectType> matches;
std::cerr << "Testing SMILES: " << smi << " " << smarts << std::endl;
TEST_ASSERT(SubstructMatch(*mol, *query, matches, uniquify,
recursionPossible, useChirality));
}

View File

@@ -6674,11 +6674,11 @@ M END
mol = Chem.MolFromSmiles('c1cc[nH]c1')
nops = Chem.AdjustQueryParameters.NoAdjustments()
nmol = Chem.AdjustQueryProperties(mol, nops)
self.assertEqual(Chem.MolToSmarts(nmol), "[#6]1:[#6]:[#6]:[#7H]:[#6]:1")
self.assertEqual(Chem.MolToSmarts(nmol), "[#6]1:[#6]:[#6]:[#7]:[#6]:1")
nops.adjustConjugatedFiveRings = True
nmol = Chem.AdjustQueryProperties(mol, nops)
self.assertEqual(Chem.MolToSmarts(nmol), "[#6]1-,=,:[#6]-,=,:[#6]-,=,:[#7H]-,=,:[#6]-,=,:1")
self.assertEqual(Chem.MolToSmarts(nmol), "[#6]1-,=,:[#6]-,=,:[#6]-,=,:[#7]-,=,:[#6]-,=,:1")
def testFindPotentialStereo(self):
mol = Chem.MolFromSmiles('C[C@H](F)C=CC')
@@ -8528,10 +8528,10 @@ M END
self.assertIsNotNone(mol)
ps = Chem.SmilesWriteParams()
sma = Chem.MolToSmarts(mol, ps)
self.assertEqual(sma, '[#7H3]->[Fe]-[#7]')
self.assertEqual(sma, '[#7]->[Fe]-[#7]')
ps.includeDativeBonds = False
sma = Chem.MolToSmarts(mol, ps)
self.assertEqual(sma, '[#7H3]-[Fe]-[#7]')
self.assertEqual(sma, '[#7]-[Fe]-[#7]')
def testMolToV2KMolBlock(self):
mol = Chem.MolFromSmiles('[NH3]->[Fe]')

View File

@@ -439,7 +439,7 @@ TEST_CASE("MDL five-rings") {
// clang-format off
std::vector<extuple> examples = {
// no queries, no change
extuple{"adjustqueryprops_MDLfivering_1.mol","[#7H]1:[#6]:[#6]:[#6]:[#6]:1",""},
extuple{"adjustqueryprops_MDLfivering_1.mol","[#7]1:[#6]:[#6]:[#6]:[#6]:1",""},
// Q atom, no change
extuple{"adjustqueryprops_MDLfivering_2.mol","[!#6&!#1]1:[#6]:[#6]:[#6]:[#6]:1",""},
// A atom, this one changes
@@ -792,7 +792,7 @@ TEST_CASE("makeAtomsGeneric") {
auto m = "C[CH3:1]"_smiles;
REQUIRE(m);
MolOps::adjustQueryProperties(*m, &ps);
CHECK(MolToSmarts(*m) == "*-[#6H3:1]");
CHECK(MolToSmarts(*m) == "*-[#6:1]");
}
}

View File

@@ -2581,7 +2581,7 @@ reaction to be reconstructed:
>>> newRxn = AllChem.ReactionFromPNGString(png)
>>> AllChem.ReactionToSmarts(newRxn)
'[#6H:5]1:[#6H:6]:[#6:7]2:[#6H:8]:[#7:9]:[#6H:10]:[#6H:11]:[#6:12]:2:[#6:3](:[#6H:4]:1)-[#6:2](=[#8:1])-[#8].[#7-:13]=[#7+:14]=[#7-:15]>[#6](-[#17])-[#17].[#6](=[#8])(-[#6](=[#8])-[#17])-[#17]>[#6H:5]1:[#6H:6]:[#6:7]2:[#6H:8]:[#7:9]:[#6H:10]:[#6H:11]:[#6:12]:2:[#6:3](:[#6H:4]:1)-[#6:2](=[#8:1])-[#7:13]=[#7+:14]=[#7-:15]'
'[#6:5]1:[#6:6]:[#6:7]2:[#6:8]:[#7:9]:[#6:10]:[#6:11]:[#6:12]:2:[#6:3](:[#6:4]:1)-[#6:2](=[#8:1])-[#8].[#7-:13]=[#7+:14]=[#7-:15]>[#6](-[#17])-[#17].[#6](=[#8])(-[#6](=[#8])-[#17])-[#17]>[#6:5]1:[#6:6]:[#6:7]2:[#6:8]:[#7:9]:[#6:10]:[#6:11]:[#6:12]:2:[#6:3](:[#6:4]:1)-[#6:2](=[#8:1])-[#7:13]=[#7+:14]=[#7-:15]'
Advanced Reaction Functionality
===============================

View File

@@ -22,6 +22,9 @@ GitHub)
`Chem.CanonicalRankAtoms(mol, breakTies=False)`.
- The behavior of H removal has changed slightly: hydrides will no longer removed
by default, as this changes the global charge of the mol.
- `MolToSmarts()` no longer adds implicit hydrogens to atoms without queries. The
one exception to this is for chiral atoms, which will still have an implicit H
added if present.
## New Features and Enhancements: