#
#  Copyright (C) 2003-2023  Greg Landrum and other RDKit contributors
#         All Rights Reserved
#
""" This is a rough coverage test of the python wrapper

it's intended to be shallow, but broad

"""

import doctest
import gc
import gzip
import importlib.util
import logging
import os
import pickle
import sys
import tempfile
import unittest
from contextlib import contextmanager
from datetime import datetime, timedelta
from io import BytesIO, StringIO

import rdkit.Chem.rdDepictor
from rdkit import Chem, DataStructs, RDConfig, __version__, rdBase
from rdkit.Chem import rdqueries
from rdkit.Chem import AllChem
from rdkit.Chem.Scaffolds import MurckoScaffold

import numpy as np

# Boost functions are NOT found by doctest, this "fixes" them
#  by adding the doctests to a fake module
spec = importlib.util.spec_from_loader("TestReplaceCore", loader=None)
TestReplaceCore = importlib.util.module_from_spec(spec)
code = """
from rdkit.Chem import ReplaceCore
def ReplaceCore(*a, **kw):
    '''%s
    '''
    return Chem.ReplaceCore(*a, **kw)
""" % "\n".join([x.lstrip() for x in Chem.ReplaceCore.__doc__.split("\n")])
exec(code, TestReplaceCore.__dict__)


@contextmanager
def log_to_python(level=None):
  """
    Temporarily redirect logging to Python streams, optionally
    setting a specific log level.
  """
  rdBase.LogToPythonLogger()
  pylog = logging.getLogger("rdkit")
  if level is not None:
    original_level = pylog.level
    pylog.setLevel(level)

  yield pylog

  if level is not None:
    pylog.setLevel(original_level)
  rdBase.LogToCppStreams()


@contextmanager
def capture_logging(level=None):
  """
    Temporarily redirect logging to a Python StringIO, optionally
    setting a specific log level.
  """
  log_stream = StringIO()
  stream_handler = logging.StreamHandler(stream=log_stream)

  with log_to_python(level) as pylog:
    pylog.addHandler(stream_handler)

    yield log_stream

    pylog.removeHandler(stream_handler)


def load_tests(loader, tests, ignore):
  tests.addTests(doctest.DocTestSuite(TestReplaceCore))
  return tests


def feq(v1, v2, tol2=1e-4):
  return abs(v1 - v2) <= tol2


def getTotalFormalCharge(mol):
  totalFormalCharge = 0
  for atom in mol.GetAtoms():
    totalFormalCharge += atom.GetFormalCharge()
  return totalFormalCharge


def cmpFormalChargeBondOrder(self, mol1, mol2):
  self.assertEqual(mol1.GetNumAtoms(), mol2.GetNumAtoms())
  self.assertEqual(mol1.GetNumBonds(), mol2.GetNumBonds())
  for i in range(mol1.GetNumAtoms()):
    self.assertEqual(
      mol1.GetAtomWithIdx(i).GetFormalCharge(),
      mol2.GetAtomWithIdx(i).GetFormalCharge())
  for i in range(mol1.GetNumBonds()):
    self.assertEqual(mol1.GetBondWithIdx(i).GetBondType(), mol2.GetBondWithIdx(i).GetBondType())


def setResidueFormalCharge(mol, res, fc):
  for query in res:
    matches = mol.GetSubstructMatches(query)
    for match in matches:
      mol.GetAtomWithIdx(match[-1]).SetFormalCharge(fc)


def getBtList2(resMolSuppl):
  btList2 = []
  while (not resMolSuppl.atEnd()):
    resMol = next(resMolSuppl)
    bt = []
    for bond in resMol.GetBonds():
      bt.append(int(bond.GetBondTypeAsDouble()))
    btList2.append(bt)
  for i in range(len(btList2)):
    same = True
    for j in range(len(btList2[i])):
      if (not i):
        continue
      if (same):
        same = (btList2[i][j] == btList2[i - 1][j])
    if (i and same):
      return None
  return btList2


class TestCase(unittest.TestCase):

  def test0Except(self):

    with self.assertRaises(IndexError):
      Chem.tossit()

  def test1Table(self):

    tbl = Chem.GetPeriodicTable()
    self.assertTrue(tbl)

    self.assertTrue(feq(tbl.GetAtomicWeight(6), 12.011))
    self.assertTrue(feq(tbl.GetAtomicWeight("C"), 12.011))
    self.assertTrue(tbl.GetAtomicNumber('C') == 6)
    self.assertTrue(feq(tbl.GetRvdw(6), 1.7))
    self.assertTrue(feq(tbl.GetRvdw("C"), 1.7))
    self.assertTrue(feq(tbl.GetRcovalent(6), 0.76))
    self.assertTrue(feq(tbl.GetRcovalent("C"), 0.76))
    self.assertTrue(tbl.GetDefaultValence(6) == 4)
    self.assertTrue(tbl.GetDefaultValence("C") == 4)
    self.assertTrue(tuple(tbl.GetValenceList(6)) == (4, ))
    self.assertTrue(tuple(tbl.GetValenceList("C")) == (4, ))
    self.assertTrue(tuple(tbl.GetValenceList(16)) == (2, 4, 6))
    self.assertTrue(tuple(tbl.GetValenceList("S")) == (2, 4, 6))
    self.assertTrue(tbl.GetNOuterElecs(6) == 4)
    self.assertTrue(tbl.GetNOuterElecs("C") == 4)
    self.assertTrue(tbl.GetMostCommonIsotope(6) == 12)
    self.assertTrue(tbl.GetMostCommonIsotope('C') == 12)
    self.assertTrue(tbl.GetMostCommonIsotopeMass(6) == 12.0)
    self.assertTrue(tbl.GetMostCommonIsotopeMass('C') == 12.0)
    self.assertTrue(tbl.GetAbundanceForIsotope(6, 12) == 98.93)
    self.assertTrue(tbl.GetAbundanceForIsotope('C', 12) == 98.93)
    self.assertTrue(feq(tbl.GetRb0(6), 0.77))
    self.assertTrue(feq(tbl.GetRb0("C"), 0.77))
    self.assertTrue(tbl.GetElementSymbol(6) == 'C')
    self.assertTrue(tbl.GetElementName(6) == 'Carbon')
    self.assertTrue(tbl.GetRow(6) == 2)
    self.assertTrue(tbl.GetRow("C") == 2)

  def test2Atom(self):
    atom = Chem.Atom(6)
    self.assertTrue(atom)
    self.assertTrue(atom.GetAtomicNum() == 6)
    atom.SetAtomicNum(8)
    self.assertTrue(atom.GetAtomicNum() == 8)

    atom = Chem.Atom("C")
    self.assertTrue(atom)
    self.assertTrue(atom.GetAtomicNum() == 6)

  def test3Bond(self):
    # No longer relevant, bonds are not constructible from Python
    pass

  def test4Mol(self):
    mol = Chem.Mol()
    self.assertTrue(mol)

  def test5Smiles(self):
    mol = Chem.MolFromSmiles('n1ccccc1')
    self.assertTrue(mol)
    self.assertTrue(mol.GetNumAtoms() == 6)
    self.assertTrue(mol.GetNumAtoms(1) == 6)
    self.assertTrue(mol.GetNumAtoms(0) == 11)
    at = mol.GetAtomWithIdx(2)
    self.assertTrue(at.GetAtomicNum() == 6)
    at = mol.GetAtomWithIdx(0)
    self.assertTrue(at.GetAtomicNum() == 7)

  def _test6Bookmarks(self):
    mol = Chem.MolFromSmiles('n1ccccc1')
    self.assertTrue(mol)

    self.assertTrue(not mol.HasAtomBookmark(0))
    mol.SetAtomBookmark(mol.GetAtomWithIdx(0), 0)
    mol.SetAtomBookmark(mol.GetAtomWithIdx(1), 1)
    self.assertTrue(mol.HasAtomBookmark(0))
    self.assertTrue(mol.HasAtomBookmark(1))

    if 1:
      self.assertTrue(not mol.HasBondBookmark(0))
      self.assertTrue(not mol.HasBondBookmark(1))
      mol.SetBondBookmark(mol.GetBondWithIdx(0), 0)
      mol.SetBondBookmark(mol.GetBondWithIdx(1), 1)
      self.assertTrue(mol.HasBondBookmark(0))
      self.assertTrue(mol.HasBondBookmark(1))

    at = mol.GetAtomWithBookmark(0)
    self.assertTrue(at)
    self.assertTrue(at.GetAtomicNum() == 7)
    mol.ClearAtomBookmark(0)
    self.assertTrue(not mol.HasAtomBookmark(0))
    self.assertTrue(mol.HasAtomBookmark(1))
    mol.ClearAllAtomBookmarks()
    self.assertTrue(not mol.HasAtomBookmark(0))
    self.assertTrue(not mol.HasAtomBookmark(1))

    mol.SetAtomBookmark(mol.GetAtomWithIdx(1), 1)

    if 1:
      self.assertTrue(mol.HasBondBookmark(0))
      self.assertTrue(mol.HasBondBookmark(1))
      bond = mol.GetBondWithBookmark(0)
      self.assertTrue(bond)
      mol.ClearBondBookmark(0)
      self.assertTrue(not mol.HasBondBookmark(0))
      self.assertTrue(mol.HasBondBookmark(1))
      mol.ClearAllBondBookmarks()
      self.assertTrue(not mol.HasBondBookmark(0))
      self.assertTrue(not mol.HasBondBookmark(1))

      self.assertTrue(mol.HasAtomBookmark(1))

  def test7Atom(self):
    mol = Chem.MolFromSmiles('n1ccccc1C[CH2-]')
    self.assertTrue(mol)
    Chem.SanitizeMol(mol)
    a0 = mol.GetAtomWithIdx(0)
    a1 = mol.GetAtomWithIdx(1)
    a6 = mol.GetAtomWithIdx(6)
    a7 = mol.GetAtomWithIdx(7)

    self.assertTrue(a0.GetAtomicNum() == 7)
    self.assertTrue(a0.GetSymbol() == 'N')
    self.assertTrue(a0.GetIdx() == 0)

    aList = [a0, a1, a6, a7]
    self.assertTrue(a0.GetDegree() == 2)
    self.assertTrue(a1.GetDegree() == 2)
    self.assertTrue(a6.GetDegree() == 2)
    self.assertTrue(a7.GetDegree() == 1)
    self.assertTrue([x.GetDegree() for x in aList] == [2, 2, 2, 1])

    self.assertTrue([x.GetTotalNumHs() for x in aList] == [0, 1, 2, 2])
    self.assertTrue([x.GetNumImplicitHs() for x in aList] == [0, 1, 2, 0])
    self.assertTrue([x.GetExplicitValence() for x in aList] == [3, 3, 2, 3])
    self.assertTrue([x.GetImplicitValence() for x in aList] == [0, 1, 2, 0])
    self.assertTrue([x.GetFormalCharge() for x in aList] == [0, 0, 0, -1])
    self.assertTrue([x.GetNoImplicit() for x in aList] == [0, 0, 0, 1])
    self.assertTrue([x.GetNumExplicitHs() for x in aList] == [0, 0, 0, 2])
    self.assertTrue([x.GetIsAromatic() for x in aList] == [1, 1, 0, 0])
    self.assertTrue([x.GetHybridization() for x in aList] == [
      Chem.HybridizationType.SP2, Chem.HybridizationType.SP2, Chem.HybridizationType.SP3,
      Chem.HybridizationType.SP3
    ], [x.GetHybridization() for x in aList])

  def test8Bond(self):
    mol = Chem.MolFromSmiles('n1ccccc1CC(=O)O')
    self.assertTrue(mol)
    Chem.SanitizeMol(mol)
    # note bond numbering is funny because of ring closure
    b0 = mol.GetBondWithIdx(0)
    b6 = mol.GetBondWithIdx(6)
    b7 = mol.GetBondWithIdx(7)
    b8 = mol.GetBondWithIdx(8)

    bList = [b0, b6, b7, b8]
    self.assertTrue(
      [x.GetBondType() for x in bList] ==
      [Chem.BondType.AROMATIC, Chem.BondType.SINGLE, Chem.BondType.DOUBLE, Chem.BondType.SINGLE])
    self.assertTrue([x.GetIsAromatic() for x in bList] == [1, 0, 0, 0])
    self.assertEqual(bList[0].GetBondTypeAsDouble(), 1.5)
    self.assertEqual(bList[1].GetBondTypeAsDouble(), 1.0)
    self.assertEqual(bList[2].GetBondTypeAsDouble(), 2.0)

    self.assertTrue([x.GetIsConjugated() != 0 for x in bList] == [1, 0, 1, 1],
                    [x.GetIsConjugated() != 0 for x in bList])
    self.assertTrue([x.GetBeginAtomIdx() for x in bList] == [0, 6, 7, 7],
                    [x.GetBeginAtomIdx() for x in bList])
    self.assertTrue([x.GetBeginAtom().GetIdx() for x in bList] == [0, 6, 7, 7])
    self.assertTrue([x.GetEndAtomIdx() for x in bList] == [1, 7, 8, 9])
    self.assertTrue([x.GetEndAtom().GetIdx() for x in bList] == [1, 7, 8, 9])

  def test9Smarts(self):
    query1 = Chem.MolFromSmarts('C(=O)O')
    self.assertTrue(query1)
    query2 = Chem.MolFromSmarts('C(=O)[O,N]')
    self.assertTrue(query2)
    query3 = Chem.MolFromSmarts('[$(C(=O)O)]')
    self.assertTrue(query3)

    mol = Chem.MolFromSmiles('CCC(=O)O')
    self.assertTrue(mol)

    self.assertTrue(mol.HasSubstructMatch(query1))
    self.assertTrue(mol.HasSubstructMatch(query2))
    self.assertTrue(mol.HasSubstructMatch(query3))

    mol = Chem.MolFromSmiles('CCC(=O)N')
    self.assertTrue(mol)

    self.assertTrue(not mol.HasSubstructMatch(query1))
    self.assertTrue(mol.HasSubstructMatch(query2))
    self.assertTrue(not mol.HasSubstructMatch(query3))

  def test10Iterators(self):
    mol = Chem.MolFromSmiles('CCOC')
    self.assertTrue(mol)

    for atom in mol.GetAtoms():
      self.assertTrue(atom)
    ats = mol.GetAtoms()
    ats[1]
    with self.assertRaisesRegex(IndexError, ""):
      ats[12]

    for bond in mol.GetBonds():
      self.assertTrue(bond)
    bonds = mol.GetBonds()
    bonds[1]
    with self.assertRaisesRegex(IndexError, ""):
      bonds[12]

  def test11MolOps(self):
    mol = Chem.MolFromSmiles('C1=CC=C(C=C1)P(C2=CC=CC=C2)C3=CC=CC=C3')
    self.assertTrue(mol)
    smi = Chem.MolToSmiles(mol)
    Chem.SanitizeMol(mol)
    nr = Chem.GetSymmSSSR(mol)
    self.assertTrue((len(nr) == 3))
    nr = Chem.GetSSSR(mol)
    self.assertTrue((len(nr) == 3))

  def test12Smarts(self):
    query1 = Chem.MolFromSmarts('C(=O)O')
    self.assertTrue(query1)
    query2 = Chem.MolFromSmarts('C(=O)[O,N]')
    self.assertTrue(query2)
    query3 = Chem.MolFromSmarts('[$(C(=O)O)]')
    self.assertTrue(query3)

    mol = Chem.MolFromSmiles('CCC(=O)O')
    self.assertTrue(mol)

    self.assertTrue(mol.HasSubstructMatch(query1))
    self.assertTrue(mol.GetSubstructMatch(query1) == (2, 3, 4))
    self.assertTrue(mol.HasSubstructMatch(query2))
    self.assertTrue(mol.GetSubstructMatch(query2) == (2, 3, 4))
    self.assertTrue(mol.HasSubstructMatch(query3))
    self.assertTrue(mol.GetSubstructMatch(query3) == (2, ))

    mol = Chem.MolFromSmiles('CCC(=O)N')
    self.assertTrue(mol)

    self.assertTrue(not mol.HasSubstructMatch(query1))
    self.assertTrue(not mol.GetSubstructMatch(query1))
    self.assertTrue(mol.HasSubstructMatch(query2))
    self.assertTrue(mol.GetSubstructMatch(query2) == (2, 3, 4))
    self.assertTrue(not mol.HasSubstructMatch(query3))

    mol = Chem.MolFromSmiles('OC(=O)CC(=O)O')
    self.assertTrue(mol)
    self.assertTrue(mol.HasSubstructMatch(query1))
    self.assertTrue(mol.GetSubstructMatch(query1) == (1, 2, 0))
    self.assertTrue(mol.GetSubstructMatches(query1) == ((1, 2, 0), (4, 5, 6)))
    self.assertTrue(mol.HasSubstructMatch(query2))
    self.assertTrue(mol.GetSubstructMatch(query2) == (1, 2, 0))
    self.assertTrue(mol.GetSubstructMatches(query2) == ((1, 2, 0), (4, 5, 6)))
    self.assertTrue(mol.HasSubstructMatch(query3))
    self.assertTrue(mol.GetSubstructMatches(query3) == ((1, ), (4, )))

  def test13Smarts(self):
    # previous smarts problems:
    query = Chem.MolFromSmarts('N(=,-C)')
    self.assertTrue(query)
    mol = Chem.MolFromSmiles('N#C')
    self.assertTrue(not mol.HasSubstructMatch(query))
    mol = Chem.MolFromSmiles('N=C')
    self.assertTrue(mol.HasSubstructMatch(query))
    mol = Chem.MolFromSmiles('NC')
    self.assertTrue(mol.HasSubstructMatch(query))

    query = Chem.MolFromSmarts('[Cl,$(O)]')
    mol = Chem.MolFromSmiles('C(=O)O')
    self.assertTrue(len(mol.GetSubstructMatches(query)) == 2)
    mol = Chem.MolFromSmiles('C(=N)N')
    self.assertTrue(len(mol.GetSubstructMatches(query)) == 0)

    query = Chem.MolFromSmarts('[$([O,S]-[!$(*=O)])]')
    mol = Chem.MolFromSmiles('CC(S)C(=O)O')
    self.assertTrue(len(mol.GetSubstructMatches(query)) == 1)
    mol = Chem.MolFromSmiles('C(=O)O')
    self.assertTrue(len(mol.GetSubstructMatches(query)) == 0)

  def test14Hs(self):
    m = Chem.MolFromSmiles('CC(=O)[OH]')
    self.assertEqual(m.GetNumAtoms(), 4)
    m2 = Chem.AddHs(m)
    self.assertEqual(m2.GetNumAtoms(), 8)
    m2 = Chem.RemoveHs(m2)
    self.assertEqual(m2.GetNumAtoms(), 4)

    m = Chem.MolFromSmiles('CC[H]', False)
    self.assertEqual(m.GetNumAtoms(), 3)
    m2 = Chem.MergeQueryHs(m)
    self.assertEqual(m2.GetNumAtoms(), 2)
    self.assertTrue(m2.GetAtomWithIdx(1).HasQuery())

    m = Chem.MolFromSmiles('CC[H]', False)
    self.assertEqual(m.GetNumAtoms(), 3)
    m1 = Chem.RemoveHs(m)
    self.assertEqual(m1.GetNumAtoms(), 2)
    self.assertEqual(m1.GetAtomWithIdx(1).GetNumExplicitHs(), 0)
    m1 = Chem.RemoveHs(m, updateExplicitCount=True)
    self.assertEqual(m1.GetNumAtoms(), 2)
    self.assertEqual(m1.GetAtomWithIdx(1).GetNumExplicitHs(), 1)

    # test merging of mapped hydrogens
    m = Chem.MolFromSmiles('CC[H]', False)
    m.GetAtomWithIdx(2).SetProp("molAtomMapNumber", "1")
    self.assertEqual(m.GetNumAtoms(), 3)
    m2 = Chem.MergeQueryHs(m, mergeUnmappedOnly=True)
    self.assertTrue(m2 is not None)
    self.assertEqual(m2.GetNumAtoms(), 3)
    self.assertFalse(m2.GetAtomWithIdx(1).HasQuery())

    # here the hydrogen is unmapped
    #  should be the same as merging all hydrogens
    m = Chem.MolFromSmiles('CC[H]', False)
    m.GetAtomWithIdx(1).SetProp("molAtomMapNumber", "1")
    self.assertEqual(m.GetNumAtoms(), 3)
    m2 = Chem.MergeQueryHs(m, mergeUnmappedOnly=True)
    self.assertTrue(m2 is not None)
    self.assertEqual(m2.GetNumAtoms(), 2)
    self.assertTrue(m2.GetAtomWithIdx(1).HasQuery())

    # test merging of isotopes, by default deuterium will not be merged
    m = Chem.MolFromSmiles('CC[2H]', False)
    self.assertEqual(m.GetNumAtoms(), 3)
    m2 = Chem.MergeQueryHs(m)
    self.assertTrue(m2 is not None)
    self.assertEqual(m2.GetNumAtoms(), 3)
    self.assertFalse(m2.GetAtomWithIdx(1).HasQuery())

    # here deuterium is merged
    # should be the same as merging all hydrogens
    m = Chem.MolFromSmiles('CC[2H]', False)
    self.assertEqual(m.GetNumAtoms(), 3)
    m2 = Chem.MergeQueryHs(m, mergeIsotopes=True)
    self.assertTrue(m2 is not None)
    self.assertEqual(m2.GetNumAtoms(), 2)
    self.assertTrue(m2.GetAtomWithIdx(1).HasQuery())

    # test github758
    m = Chem.MolFromSmiles('CCC')
    self.assertEqual(m.GetNumAtoms(), 3)
    m = Chem.AddHs(m, onlyOnAtoms=(0, 2))
    self.assertEqual(m.GetNumAtoms(), 9)
    self.assertEqual(m.GetAtomWithIdx(0).GetDegree(), 4)
    self.assertEqual(m.GetAtomWithIdx(2).GetDegree(), 4)
    self.assertEqual(m.GetAtomWithIdx(1).GetDegree(), 2)

  def test15Neighbors(self):
    m = Chem.MolFromSmiles('CC(=O)[OH]')
    self.assertTrue(m.GetNumAtoms() == 4)

    a = m.GetAtomWithIdx(1)
    ns = a.GetNeighbors()
    self.assertTrue(len(ns) == 3)

    bs = a.GetBonds()
    self.assertTrue(len(bs) == 3)

    for b in bs:
      try:
        a2 = b.GetOtherAtom(a)
      except Exception:
        a2 = None
      self.assertTrue(a2)
    self.assertTrue(len(bs) == 3)

  def test16Pickle(self):
    import pickle
    m = Chem.MolFromSmiles('C1=CN=CC=C1')
    pkl = pickle.dumps(m)
    m2 = pickle.loads(pkl)
    self.assertTrue(type(m2) == Chem.Mol)
    smi1 = Chem.MolToSmiles(m)
    smi2 = Chem.MolToSmiles(m2)
    self.assertTrue(smi1 == smi2)

    pkl = pickle.dumps(Chem.RWMol(m))
    m2 = pickle.loads(pkl)
    self.assertTrue(type(m2) == Chem.RWMol)
    smi1 = Chem.MolToSmiles(m)
    smi2 = Chem.MolToSmiles(m2)
    self.assertTrue(smi1 == smi2)

  def test16Props(self):
    m = Chem.MolFromSmiles('C1=CN=CC=C1')
    self.assertTrue(not m.HasProp('prop1'))
    self.assertTrue(not m.HasProp('prop2'))
    self.assertTrue(not m.HasProp('prop2'))
    m.SetProp('prop1', 'foob')
    self.assertTrue(not m.HasProp('prop2'))
    self.assertTrue(m.HasProp('prop1'))
    self.assertTrue(m.GetProp('prop1') == 'foob')
    self.assertTrue(not m.HasProp('propo'))
    try:
      m.GetProp('prop2')
    except KeyError:
      ok = 1
    else:
      ok = 0
    self.assertTrue(ok)

    # test computed properties
    m.SetProp('cprop1', 'foo', 1)
    m.SetProp('cprop2', 'foo2', 1)

    m.ClearComputedProps()
    self.assertTrue(not m.HasProp('cprop1'))
    self.assertTrue(not m.HasProp('cprop2'))

    m.SetDoubleProp("a", 2.0)
    self.assertTrue(m.GetDoubleProp("a") == 2.0)

    try:
      self.assertTrue(m.GetIntProp("a") == 2.0)
      raise Exception("Expected runtime exception")
    except ValueError:
      pass

    try:
      self.assertTrue(m.GetUnsignedProp("a") == 2.0)
      raise Exception("Expected runtime exception")
    except ValueError:
      pass

    m.SetDoubleProp("a", -2)
    self.assertTrue(m.GetDoubleProp("a") == -2.0)
    m.SetIntProp("a", -2)
    self.assertTrue(m.GetIntProp("a") == -2)

    try:
      m.SetUnsignedProp("a", -2)
      raise Exception("Expected failure with negative unsigned number")
    except OverflowError:
      pass

    m.SetBoolProp("a", False)
    self.assertFalse(m.GetBoolProp("a"))

    self.assertEqual(m.GetPropsAsDict(), {'a': False, 'prop1': 'foob'})
    m.SetDoubleProp("b", 1000.0)
    m.SetUnsignedProp("c", 2000)
    m.SetIntProp("d", -2)
    m.SetUnsignedProp("e", 2, True)
    self.assertEqual(m.GetPropsAsDict(False, True), {
      'a': False,
      'c': 2000,
      'b': 1000.0,
      'e': 2,
      'd': -2,
      'prop1': 'foob'
    })
    m = Chem.MolFromSmiles('C1=CN=CC=C1')
    m.SetProp("int", "1000")
    m.SetProp("double", "10000.123")
    self.assertEqual(m.GetPropsAsDict(), {"int": 1000, "double": 10000.123})

    self.assertEqual(type(m.GetPropsAsDict()['int']), int)
    self.assertEqual(type(m.GetPropsAsDict()['double']), float)

  def test17Kekulize(self):
    m = Chem.MolFromSmiles('c1ccccc1')
    smi = Chem.MolToSmiles(m)
    self.assertTrue(smi == 'c1ccccc1')

    Chem.Kekulize(m)
    smi = Chem.MolToSmiles(m)
    self.assertTrue(smi == 'c1ccccc1')

    m = Chem.MolFromSmiles('c1ccccc1')
    smi = Chem.MolToSmiles(m)
    self.assertTrue(smi == 'c1ccccc1')

    Chem.Kekulize(m, 1)
    smi = Chem.MolToSmiles(m)
    self.assertTrue(smi == 'C1=CC=CC=C1', smi)

  def test18Paths(self):

    m = Chem.MolFromSmiles("C1CC2C1CC2")
    #self.assertTrue(len(Chem.FindAllPathsOfLengthN(m,1,useBonds=1))==7)
    #print(Chem.FindAllPathsOfLengthN(m,3,useBonds=0))
    self.assertTrue(
      len(Chem.FindAllPathsOfLengthN(m, 2, useBonds=1)) == 10,
      Chem.FindAllPathsOfLengthN(m, 2, useBonds=1))
    self.assertTrue(len(Chem.FindAllPathsOfLengthN(m, 3, useBonds=1)) == 14)

    m = Chem.MolFromSmiles('C1CC1C')
    self.assertTrue(m)
    self.assertTrue(len(Chem.FindAllPathsOfLengthN(m, 1, useBonds=1)) == 4)
    self.assertTrue(len(Chem.FindAllPathsOfLengthN(m, 2, useBonds=1)) == 5)
    self.assertTrue(
      len(Chem.FindAllPathsOfLengthN(m, 3, useBonds=1)) == 3,
      Chem.FindAllPathsOfLengthN(m, 3, useBonds=1))
    self.assertTrue(
      len(Chem.FindAllPathsOfLengthN(m, 4, useBonds=1)) == 1,
      Chem.FindAllPathsOfLengthN(m, 4, useBonds=1))
    self.assertTrue(
      len(Chem.FindAllPathsOfLengthN(m, 5, useBonds=1)) == 0,
      Chem.FindAllPathsOfLengthN(m, 5, useBonds=1))

    #
    #  Hexane example from Hall-Kier Rev.Comp.Chem. paper
    #  Rev. Comp. Chem. vol 2, 367-422, (1991)
    #
    m = Chem.MolFromSmiles("CCCCCC")
    self.assertTrue(len(Chem.FindAllPathsOfLengthN(m, 1, useBonds=1)) == 5)
    self.assertTrue(len(Chem.FindAllPathsOfLengthN(m, 2, useBonds=1)) == 4)
    self.assertTrue(len(Chem.FindAllPathsOfLengthN(m, 3, useBonds=1)) == 3)

    m = Chem.MolFromSmiles("CCC(C)CC")
    self.assertTrue(len(Chem.FindAllPathsOfLengthN(m, 1, useBonds=1)) == 5)
    self.assertTrue(len(Chem.FindAllPathsOfLengthN(m, 2, useBonds=1)) == 5)
    self.assertTrue(
      len(Chem.FindAllPathsOfLengthN(m, 3, useBonds=1)) == 4,
      Chem.FindAllPathsOfLengthN(m, 3, useBonds=1))

    m = Chem.MolFromSmiles("CCCC(C)C")
    self.assertTrue(len(Chem.FindAllPathsOfLengthN(m, 1, useBonds=1)) == 5)
    self.assertTrue(len(Chem.FindAllPathsOfLengthN(m, 2, useBonds=1)) == 5)
    self.assertTrue(len(Chem.FindAllPathsOfLengthN(m, 3, useBonds=1)) == 3)

    m = Chem.MolFromSmiles("CC(C)C(C)C")
    self.assertTrue(len(Chem.FindAllPathsOfLengthN(m, 1, useBonds=1)) == 5)
    self.assertTrue(len(Chem.FindAllPathsOfLengthN(m, 2, useBonds=1)) == 6)
    self.assertTrue(len(Chem.FindAllPathsOfLengthN(m, 3, useBonds=1)) == 4)

    m = Chem.MolFromSmiles("CC(C)(C)CC")
    self.assertTrue(len(Chem.FindAllPathsOfLengthN(m, 1, useBonds=1)) == 5)
    self.assertTrue(len(Chem.FindAllPathsOfLengthN(m, 2, useBonds=1)) == 7)
    self.assertTrue(
      len(Chem.FindAllPathsOfLengthN(m, 3, useBonds=1)) == 3,
      Chem.FindAllPathsOfLengthN(m, 3, useBonds=1))

    m = Chem.MolFromSmiles("C1CCCCC1")
    self.assertTrue(len(Chem.FindAllPathsOfLengthN(m, 1, useBonds=1)) == 6)
    self.assertTrue(len(Chem.FindAllPathsOfLengthN(m, 2, useBonds=1)) == 6)
    self.assertTrue(len(Chem.FindAllPathsOfLengthN(m, 3, useBonds=1)) == 6)

    m = Chem.MolFromSmiles("C1CC2C1CC2")
    self.assertTrue(len(Chem.FindAllPathsOfLengthN(m, 1, useBonds=1)) == 7)
    self.assertTrue(
      len(Chem.FindAllPathsOfLengthN(m, 2, useBonds=1)) == 10,
      Chem.FindAllPathsOfLengthN(m, 2, useBonds=1))
    self.assertTrue(len(Chem.FindAllPathsOfLengthN(m, 3, useBonds=1)) == 14)

    m = Chem.MolFromSmiles("CC2C1CCC12")
    self.assertTrue(len(Chem.FindAllPathsOfLengthN(m, 1, useBonds=1)) == 7)
    self.assertTrue(len(Chem.FindAllPathsOfLengthN(m, 2, useBonds=1)) == 11)
    # FIX: this result disagrees with the paper (which says 13),
    #   but it seems right
    self.assertTrue(
      len(Chem.FindAllPathsOfLengthN(m, 3, useBonds=1)) == 15,
      Chem.FindAllPathsOfLengthN(m, 3, useBonds=1))

  def test19Subgraphs(self):
    m = Chem.MolFromSmiles('C1CC1C')
    self.assertTrue(m)
    self.assertTrue(len(Chem.FindAllSubgraphsOfLengthN(m, 1, 0)) == 4)
    self.assertTrue(len(Chem.FindAllSubgraphsOfLengthN(m, 2)) == 5)
    self.assertTrue(len(Chem.FindAllSubgraphsOfLengthN(m, 3)) == 4)
    self.assertTrue(len(Chem.FindAllSubgraphsOfLengthN(m, 4)) == 1)
    self.assertTrue(len(Chem.FindAllSubgraphsOfLengthN(m, 5)) == 0)

    #
    #  Hexane example from Hall-Kier Rev.Comp.Chem. paper
    #  Rev. Comp. Chem. vol 2, 367-422, (1991)
    #
    m = Chem.MolFromSmiles("CCCCCC")
    self.assertTrue(len(Chem.FindAllSubgraphsOfLengthN(m, 1)) == 5)
    self.assertTrue(len(Chem.FindAllSubgraphsOfLengthN(m, 2)) == 4)
    self.assertTrue(len(Chem.FindAllSubgraphsOfLengthN(m, 3)) == 3)

    l = Chem.FindAllSubgraphsOfLengthMToN(m, 1, 3)
    self.assertEqual(len(l), 3)
    self.assertEqual(len(l[0]), 5)
    self.assertEqual(len(l[1]), 4)
    self.assertEqual(len(l[2]), 3)
    self.assertRaises(ValueError, lambda: Chem.FindAllSubgraphsOfLengthMToN(m, 4, 3))

    m = Chem.MolFromSmiles("CCC(C)CC")
    self.assertTrue(len(Chem.FindAllSubgraphsOfLengthN(m, 1)) == 5)
    self.assertTrue(len(Chem.FindAllSubgraphsOfLengthN(m, 2)) == 5)
    self.assertTrue(len(Chem.FindAllSubgraphsOfLengthN(m, 3)) == 5)

    m = Chem.MolFromSmiles("CCCC(C)C")
    self.assertTrue(len(Chem.FindAllSubgraphsOfLengthN(m, 1)) == 5)
    self.assertTrue(len(Chem.FindAllSubgraphsOfLengthN(m, 2)) == 5)
    self.assertTrue(len(Chem.FindAllSubgraphsOfLengthN(m, 3)) == 4)

    m = Chem.MolFromSmiles("CC(C)C(C)C")
    self.assertTrue(len(Chem.FindAllSubgraphsOfLengthN(m, 1)) == 5)
    self.assertTrue(len(Chem.FindAllSubgraphsOfLengthN(m, 2)) == 6)
    self.assertTrue(len(Chem.FindAllSubgraphsOfLengthN(m, 3)) == 6)

    m = Chem.MolFromSmiles("CC(C)(C)CC")
    self.assertTrue(len(Chem.FindAllSubgraphsOfLengthN(m, 1)) == 5)
    self.assertTrue(len(Chem.FindAllSubgraphsOfLengthN(m, 2)) == 7)
    self.assertTrue(
      len(Chem.FindAllSubgraphsOfLengthN(m, 3)) == 7, Chem.FindAllSubgraphsOfLengthN(m, 3))

    m = Chem.MolFromSmiles("C1CCCCC1")
    self.assertTrue(len(Chem.FindAllSubgraphsOfLengthN(m, 1)) == 6)
    self.assertTrue(len(Chem.FindAllSubgraphsOfLengthN(m, 2)) == 6)
    self.assertTrue(len(Chem.FindAllSubgraphsOfLengthN(m, 3)) == 6)
    #self.assertTrue(len(Chem.FindUniqueSubgraphsOfLengthN(m,1))==1)
    self.assertTrue(len(Chem.FindUniqueSubgraphsOfLengthN(m, 2)) == 1)
    self.assertTrue(len(Chem.FindUniqueSubgraphsOfLengthN(m, 3)) == 1)

    m = Chem.MolFromSmiles("C1CC2C1CC2")
    self.assertTrue(len(Chem.FindAllSubgraphsOfLengthN(m, 1)) == 7)
    self.assertTrue(len(Chem.FindAllSubgraphsOfLengthN(m, 2)) == 10)
    self.assertTrue(len(Chem.FindAllSubgraphsOfLengthN(m, 3)) == 16)

    m = Chem.MolFromSmiles("CC2C1CCC12")
    self.assertTrue(len(Chem.FindAllSubgraphsOfLengthN(m, 1)) == 7)
    self.assertTrue(len(Chem.FindAllSubgraphsOfLengthN(m, 2)) == 11)
    self.assertTrue(
      len(Chem.FindAllSubgraphsOfLengthN(m, 3)) == 18, len(Chem.FindAllSubgraphsOfLengthN(m, 3)))

  def test20IsInRing(self):
    m = Chem.MolFromSmiles('C1CCC1C')
    self.assertTrue(m)
    self.assertTrue(m.GetAtomWithIdx(0).IsInRingSize(4))
    self.assertTrue(m.GetAtomWithIdx(1).IsInRingSize(4))
    self.assertTrue(m.GetAtomWithIdx(2).IsInRingSize(4))
    self.assertTrue(m.GetAtomWithIdx(3).IsInRingSize(4))
    self.assertTrue(not m.GetAtomWithIdx(4).IsInRingSize(4))

    self.assertTrue(not m.GetAtomWithIdx(0).IsInRingSize(3))
    self.assertTrue(not m.GetAtomWithIdx(1).IsInRingSize(3))
    self.assertTrue(not m.GetAtomWithIdx(2).IsInRingSize(3))
    self.assertTrue(not m.GetAtomWithIdx(3).IsInRingSize(3))
    self.assertTrue(not m.GetAtomWithIdx(4).IsInRingSize(3))

    self.assertTrue(m.GetBondWithIdx(0).IsInRingSize(4))
    self.assertTrue(not m.GetBondWithIdx(3).IsInRingSize(4))
    self.assertTrue(not m.GetBondWithIdx(0).IsInRingSize(3))
    self.assertTrue(not m.GetBondWithIdx(3).IsInRingSize(3))

  def test21Robustification(self):
    ok = False
    # FIX: at the moment I can't figure out how to catch the
    # actual exception that BPL is throwing when it gets
    # invalid arguments (Boost.Python.ArgumentError)
    try:
      Chem.MolFromSmiles('C=O').HasSubstructMatch(Chem.MolFromSmarts('fiib'))
    #except ValueError:
    #  ok=True
    except Exception:
      ok = True
    self.assertTrue(ok)

  def test22DeleteSubstruct(self):
    query = Chem.MolFromSmarts('C(=O)O')
    mol = Chem.MolFromSmiles('CCC(=O)O')
    nmol = Chem.DeleteSubstructs(mol, query)

    self.assertTrue(Chem.MolToSmiles(nmol) == 'CC')

    mol = Chem.MolFromSmiles('CCC(=O)O.O=CO')
    # now delete only fragments
    nmol = Chem.DeleteSubstructs(mol, query, 1)
    self.assertTrue(Chem.MolToSmiles(nmol) == 'CCC(=O)O', Chem.MolToSmiles(nmol))

    mol = Chem.MolFromSmiles('CCC(=O)O.O=CO')
    nmol = Chem.DeleteSubstructs(mol, query, 0)
    self.assertTrue(Chem.MolToSmiles(nmol) == 'CC')

    mol = Chem.MolFromSmiles('CCCO')
    nmol = Chem.DeleteSubstructs(mol, query, 0)
    self.assertTrue(Chem.MolToSmiles(nmol) == 'CCCO')

    # Issue 96 prevented this from working:
    mol = Chem.MolFromSmiles('CCC(=O)O.O=CO')
    nmol = Chem.DeleteSubstructs(mol, query, 1)
    self.assertTrue(Chem.MolToSmiles(nmol) == 'CCC(=O)O')
    nmol = Chem.DeleteSubstructs(nmol, query, 1)
    self.assertTrue(Chem.MolToSmiles(nmol) == 'CCC(=O)O')
    nmol = Chem.DeleteSubstructs(nmol, query, 0)
    self.assertTrue(Chem.MolToSmiles(nmol) == 'CC')

  def test23MolFileParsing(self):
    fileN = os.path.join(RDConfig.RDBaseDir, 'Code', 'GraphMol', 'FileParsers', 'test_data',
                         'triazine.mol')
    with open(fileN, 'r') as inF:
      inD = inF.read()
    m1 = Chem.MolFromMolBlock(inD)
    self.assertTrue(m1 is not None)
    self.assertTrue(m1.GetNumAtoms() == 9)

    m1 = Chem.MolFromMolFile(fileN)
    self.assertTrue(m1 is not None)
    self.assertTrue(m1.GetNumAtoms() == 9)

    fileN = os.path.join(RDConfig.RDBaseDir, 'Code', 'GraphMol', 'FileParsers', 'test_data',
                         'triazine.mof')
    self.assertRaises(IOError, lambda: Chem.MolFromMolFile(fileN))

    fileN = os.path.join(RDConfig.RDBaseDir, 'Code', 'GraphMol', 'FileParsers', 'test_data',
                         'list-query.mol')
    query = Chem.MolFromMolFile(fileN)
    smi = Chem.MolToSmiles(query)
    self.assertEqual(smi, '*1ccccc1')
    smi = Chem.MolToSmarts(query)
    self.assertEqual(smi, '[#6]1:[#6]:[#6]:[#6]:[#6]:[#6,#7,#15]:1')
    smi = Chem.MolToSmarts(query, rootedAtAtom=5)
    self.assertEqual(smi, '[#6,#7,#15]1:[#6]:[#6]:[#6]:[#6]:[#6]:1')

    query = Chem.MolFromMolFile(fileN, sanitize=False)
    smi = Chem.MolToSmiles(query)
    self.assertEqual(smi, '*1=CC=CC=C1')
    query.UpdatePropertyCache()
    smi = Chem.MolToSmarts(query)
    self.assertEqual(smi, '[#6]1=[#6]-[#6]=[#6]-[#6]=[#6,#7,#15]-1')
    smi = Chem.MolToSmarts(query, rootedAtAtom=3)
    self.assertEqual(smi, '[#6]1=[#6]-[#6]=[#6]-[#6,#7,#15]=[#6]-1')
    smi = "C1=CC=CC=C1"
    mol = Chem.MolFromSmiles(smi, 0)
    self.assertTrue(mol.HasSubstructMatch(query))
    Chem.SanitizeMol(mol)
    self.assertTrue(not mol.HasSubstructMatch(query))

    mol = Chem.MolFromSmiles('N1=CC=CC=C1', 0)
    self.assertTrue(mol.HasSubstructMatch(query))
    mol = Chem.MolFromSmiles('S1=CC=CC=C1', 0)
    self.assertTrue(not mol.HasSubstructMatch(query))
    mol = Chem.MolFromSmiles('P1=CC=CC=C1', 0)
    self.assertTrue(mol.HasSubstructMatch(query))

    fileN = os.path.join(RDConfig.RDBaseDir, 'Code', 'GraphMol', 'FileParsers', 'test_data',
                         'issue123.mol')
    mol = Chem.MolFromMolFile(fileN)
    self.assertTrue(mol)
    self.assertEqual(mol.GetNumAtoms(), 23)
    mol = Chem.MolFromMolFile(fileN, removeHs=False)
    self.assertTrue(mol)
    self.assertEqual(mol.GetNumAtoms(), 39)

  # test23 was for Chem.DaylightFingerprint, which is deprecated

  def test24RDKFingerprint(self):
    from rdkit import DataStructs
    m1 = Chem.MolFromSmiles('C1=CC=CC=C1')
    fp1 = Chem.RDKFingerprint(m1)
    self.assertTrue(len(fp1) == 2048)
    m2 = Chem.MolFromSmiles('C1=CC=CC=C1')
    fp2 = Chem.RDKFingerprint(m2)

    tmp = DataStructs.TanimotoSimilarity(fp1, fp2)
    self.assertTrue(tmp == 1.0, tmp)

    m2 = Chem.MolFromSmiles('C1=CC=CC=N1')
    fp2 = Chem.RDKFingerprint(m2)
    self.assertTrue(len(fp2) == 2048)
    tmp = DataStructs.TanimotoSimilarity(fp1, fp2)
    self.assertTrue(tmp < 1.0, tmp)
    self.assertTrue(tmp > 0.0, tmp)

    fp3 = Chem.RDKFingerprint(m1, tgtDensity=0.3)
    self.assertTrue(len(fp3) < 2048)

    m1 = Chem.MolFromSmiles('C1=CC=CC=C1')
    fp1 = Chem.RDKFingerprint(m1)
    m2 = Chem.MolFromSmiles('C1=CC=CC=N1')
    fp2 = Chem.RDKFingerprint(m2)
    self.assertNotEqual(fp1, fp2)

    atomInvariants = [1] * 6
    fp1 = Chem.RDKFingerprint(m1, atomInvariants=atomInvariants)
    fp2 = Chem.RDKFingerprint(m2, atomInvariants=atomInvariants)
    self.assertEqual(fp1, fp2)

    m2 = Chem.MolFromSmiles('C1CCCCN1')
    fp1 = Chem.RDKFingerprint(m1, atomInvariants=atomInvariants, useBondOrder=False)
    fp2 = Chem.RDKFingerprint(m2, atomInvariants=atomInvariants, useBondOrder=False)
    self.assertEqual(fp1, fp2)

    # rooted at atom
    m1 = Chem.MolFromSmiles('CCCCCO')
    fp1 = Chem.RDKFingerprint(m1, 1, 4, nBitsPerHash=1, fromAtoms=[0])
    self.assertEqual(fp1.GetNumOnBits(), 4)
    m1 = Chem.MolFromSmiles('CCCCCO')
    fp1 = Chem.RDKFingerprint(m1, 1, 4, nBitsPerHash=1, fromAtoms=[0, 5])
    self.assertEqual(fp1.GetNumOnBits(), 8)

    # test sf.net issue 270:
    fp1 = Chem.RDKFingerprint(m1, atomInvariants=[x.GetAtomicNum() + 10 for x in m1.GetAtoms()])

    # atomBits
    m1 = Chem.MolFromSmiles('CCCO')
    l = []
    fp1 = Chem.RDKFingerprint(m1, minPath=1, maxPath=2, nBitsPerHash=1, atomBits=l)
    self.assertEqual(fp1.GetNumOnBits(), 4)
    self.assertEqual(len(l), m1.GetNumAtoms())
    self.assertEqual(len(l[0]), 2)
    self.assertEqual(len(l[1]), 3)
    self.assertEqual(len(l[2]), 4)
    self.assertEqual(len(l[3]), 2)

  def test25SDMolSupplier(self):
    fileN = os.path.join(RDConfig.RDBaseDir, 'Code', 'GraphMol', 'FileParsers', 'test_data',
                         'NCI_aids_few.sdf')
    #fileN = "../FileParsers/test_data/NCI_aids_few.sdf"
    sdSup = Chem.SDMolSupplier(fileN)
    molNames = [
      "48", "78", "128", "163", "164", "170", "180", "186", "192", "203", "210", "211", "213",
      "220", "229", "256"
    ]

    chgs192 = {8: 1, 11: 1, 15: -1, 18: -1, 20: 1, 21: 1, 23: -1, 25: -1}
    i = 0
    for mol in sdSup:
      self.assertTrue(mol)
      self.assertTrue(mol.GetProp("_Name") == molNames[i])
      i += 1
      if (mol.GetProp("_Name") == "192"):
        # test parsed charges on one of the molecules
        for id in chgs192.keys():
          self.assertTrue(mol.GetAtomWithIdx(id).GetFormalCharge() == chgs192[id])
    self.assertRaises(StopIteration, lambda: next(sdSup))
    sdSup.reset()

    ns = [mol.GetProp("_Name") for mol in sdSup]
    self.assertTrue(ns == molNames)

    sdSup = Chem.SDMolSupplier(fileN, 0)
    for mol in sdSup:
      self.assertTrue(not mol.HasProp("numArom"))

    sdSup = Chem.SDMolSupplier(fileN)
    self.assertTrue(len(sdSup) == 16)
    mol = sdSup[5]
    self.assertTrue(mol.GetProp("_Name") == "170")

    # test handling of H removal:
    fileN = os.path.join(RDConfig.RDBaseDir, 'Code', 'GraphMol', 'FileParsers', 'test_data',
                         'withHs.sdf')
    sdSup = Chem.SDMolSupplier(fileN)
    m = next(sdSup)
    self.assertTrue(m)
    self.assertTrue(m.GetNumAtoms() == 23)
    m = next(sdSup)
    self.assertTrue(m)
    self.assertTrue(m.GetNumAtoms() == 28)

    sdSup = Chem.SDMolSupplier(fileN, removeHs=False)
    m = next(sdSup)
    self.assertTrue(m)
    self.assertTrue(m.GetNumAtoms() == 39)
    m = next(sdSup)
    self.assertTrue(m)
    self.assertTrue(m.GetNumAtoms() == 30)

    with open(fileN, 'rb') as dFile:
      d = dFile.read()
    sdSup.SetData(d)
    m = next(sdSup)
    self.assertTrue(m)
    self.assertTrue(m.GetNumAtoms() == 23)
    m = next(sdSup)
    self.assertTrue(m)
    self.assertTrue(m.GetNumAtoms() == 28)

    sdSup.SetData(d, removeHs=False)
    m = next(sdSup)
    self.assertTrue(m)
    self.assertTrue(m.GetNumAtoms() == 39)
    m = next(sdSup)
    self.assertTrue(m)
    self.assertTrue(m.GetNumAtoms() == 30)

    # test strictParsing1:
    fileN = os.path.join(RDConfig.RDBaseDir, 'Code', 'GraphMol', 'FileParsers', 'test_data',
                         'strictLax1.sdf')
    #strict from file
    sdSup = Chem.SDMolSupplier(fileN, strictParsing=True)

    i = 0
    for mol in sdSup:
      self.assertTrue(mol.HasProp("_Name"))
      if (i == 0):
        self.assertTrue(not mol.HasProp("ID"))
      self.assertTrue(not mol.HasProp("ANOTHER_PROPERTY"))
      i += 1
    self.assertTrue(i == 2)

    #lax from file
    sdSup = Chem.SDMolSupplier(fileN, strictParsing=False)

    i = 0
    for mol in sdSup:
      self.assertTrue(mol.HasProp("_Name"))
      self.assertTrue(mol.HasProp("ID"))
      self.assertTrue(mol.HasProp("ANOTHER_PROPERTY"))
      i += 1
    self.assertTrue(i == 2)

    #strict from text
    with open(fileN, 'rb') as dFile:
      d = dFile.read()
    sdSup = Chem.SDMolSupplier()
    sdSup.SetData(d, strictParsing=True)

    i = 0
    for mol in sdSup:
      self.assertTrue(mol.HasProp("_Name"))
      if (i == 0):
        self.assertTrue(not mol.HasProp("ID"))
      self.assertTrue(not mol.HasProp("ANOTHER_PROPERTY"))
      i += 1
    self.assertTrue(i == 2)

    #lax from text
    sdSup = Chem.SDMolSupplier()
    sdSup.SetData(d, strictParsing=False)

    i = 0
    for mol in sdSup:
      self.assertTrue(mol.HasProp("_Name"))
      self.assertTrue(mol.HasProp("ID"))
      self.assertTrue(mol.HasProp("ANOTHER_PROPERTY"))
      i += 1
    self.assertTrue(i == 2)

    # test strictParsing2:
    fileN = os.path.join(RDConfig.RDBaseDir, 'Code', 'GraphMol', 'FileParsers', 'test_data',
                         'strictLax2.sdf')
    #strict from file
    sdSup = Chem.SDMolSupplier(fileN, strictParsing=True)

    i = 0
    for mol in sdSup:
      self.assertTrue(mol.HasProp("_Name"))
      self.assertTrue(mol.HasProp("ID"))
      self.assertTrue(mol.GetProp("ID") == "Lig1")
      self.assertTrue(mol.HasProp("ANOTHER_PROPERTY"))
      self.assertTrue(
        mol.GetProp("ANOTHER_PROPERTY") == "No blank line before dollars\n"
        "$$$$\n"
        "Structure1\n"
        "csChFnd70/05230312262D")
      i += 1
    self.assertTrue(i == 1)

    #lax from file
    sdSup = Chem.SDMolSupplier(fileN, strictParsing=False)

    i = 0
    for mol in sdSup:
      self.assertTrue(mol.HasProp("_Name"))
      self.assertTrue(mol.HasProp("ID"))
      self.assertTrue(mol.GetProp("ID") == "Lig2")
      self.assertTrue(mol.HasProp("ANOTHER_PROPERTY"))
      self.assertTrue(mol.GetProp("ANOTHER_PROPERTY") == "Value2")
      i += 1
    self.assertTrue(i == 1)

    #strict from text
    with open(fileN, 'rb') as dFile:
      d = dFile.read()
    sdSup = Chem.SDMolSupplier()
    sdSup.SetData(d, strictParsing=True)

    i = 0
    for mol in sdSup:
      self.assertTrue(mol.HasProp("_Name"))
      self.assertTrue(mol.HasProp("ID"))
      self.assertTrue(mol.GetProp("ID") == "Lig1")
      self.assertTrue(mol.HasProp("ANOTHER_PROPERTY"))
      self.assertTrue(
        mol.GetProp("ANOTHER_PROPERTY") == "No blank line before dollars\n"
        "$$$$\n"
        "Structure1\n"
        "csChFnd70/05230312262D")
      i += 1
    self.assertTrue(i == 1)

    #lax from text
    sdSup = Chem.SDMolSupplier()
    sdSup.SetData(d, strictParsing=False)

    i = 0
    for mol in sdSup:
      self.assertTrue(mol.HasProp("_Name"))
      self.assertTrue(mol.HasProp("ID"))
      self.assertTrue(mol.GetProp("ID") == "Lig2")
      self.assertTrue(mol.HasProp("ANOTHER_PROPERTY"))
      self.assertTrue(mol.GetProp("ANOTHER_PROPERTY") == "Value2")
      i += 1
    self.assertTrue(i == 1)

  def test26SmiMolSupplier(self):
    fileN = os.path.join(RDConfig.RDBaseDir, 'Code', 'GraphMol', 'FileParsers', 'test_data',
                         'first_200.tpsa.csv')
    #fileN = "../FileParsers/test_data/first_200.tpsa.csv"
    smiSup = Chem.SmilesMolSupplier(fileN, ",", 0, -1)
    mol = smiSup[16]
    self.assertTrue(mol.GetProp("TPSA") == "46.25")

    mol = smiSup[8]
    self.assertTrue(mol.GetProp("TPSA") == "65.18")

    self.assertTrue(len(smiSup) == 200)

    fileN = os.path.join(RDConfig.RDBaseDir, 'Code', 'GraphMol', 'FileParsers', 'test_data',
                         'fewSmi.csv')
    #fileN = "../FileParsers/test_data/fewSmi.csv"
    smiSup = Chem.SmilesMolSupplier(fileN, delimiter=",", smilesColumn=1, nameColumn=0, titleLine=0)
    names = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10"]
    i = 0
    for mol in smiSup:
      self.assertTrue(mol.GetProp("_Name") == names[i])
      i += 1

    mol = smiSup[3]

    self.assertTrue(mol.GetProp("_Name") == "4")
    self.assertTrue(mol.GetProp("Column_2") == "82.78")

    # and test doing a supplier from text:
    with open(fileN, 'r') as inF:
      inD = inF.read()
    smiSup.SetData(inD, delimiter=",", smilesColumn=1, nameColumn=0, titleLine=0)
    names = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10"]
    i = 0
    # iteration interface:
    for mol in smiSup:
      self.assertTrue(mol.GetProp("_Name") == names[i])
      i += 1
    self.assertTrue(i == 10)
    # random access:
    mol = smiSup[3]
    self.assertTrue(len(smiSup) == 10)
    self.assertTrue(mol.GetProp("_Name") == "4")
    self.assertTrue(mol.GetProp("Column_2") == "82.78")

    # issue 113:
    smiSup.SetData(inD, delimiter=",", smilesColumn=1, nameColumn=0, titleLine=0)
    self.assertTrue(len(smiSup) == 10)

    # and test failure handling:
    inD = """mol-1,CCC
mol-2,CCCC
mol-3,fail
mol-4,CCOC
    """
    smiSup.SetData(inD, delimiter=",", smilesColumn=1, nameColumn=0, titleLine=0)
    # there are 4 entries in the supplier:
    self.assertTrue(len(smiSup) == 4)
    # but the 3rd is a None:
    self.assertTrue(smiSup[2] is None)


    text="Id SMILES Column_2\n"+\
    "mol-1 C 1.0\n"+\
    "mol-2 CC 4.0\n"+\
    "mol-4 CCCC 16.0"
    smiSup.SetData(text, delimiter=" ", smilesColumn=1, nameColumn=0, titleLine=1)
    self.assertTrue(len(smiSup) == 3)
    self.assertTrue(smiSup[0])
    self.assertTrue(smiSup[1])
    self.assertTrue(smiSup[2])
    m = [x for x in smiSup]
    self.assertTrue(smiSup[2])
    self.assertTrue(len(m) == 3)
    self.assertTrue(m[0].GetProp("Column_2") == "1.0")

    # test simple parsing and Issue 114:
    smis = ['CC', 'CCC', 'CCOC', 'CCCOCC', 'CCCOCCC']
    inD = '\n'.join(smis)
    smiSup.SetData(inD, delimiter=",", smilesColumn=0, nameColumn=-1, titleLine=0)
    self.assertTrue(len(smiSup) == 5)
    m = [x for x in smiSup]
    self.assertTrue(smiSup[4])
    self.assertTrue(len(m) == 5)

    # order dependence:
    smiSup.SetData(inD, delimiter=",", smilesColumn=0, nameColumn=-1, titleLine=0)
    self.assertTrue(smiSup[4])
    self.assertTrue(len(smiSup) == 5)

    # this was a nasty BC:
    # asking for a particular entry with a higher index than what we've
    # already seen resulted in a duplicate:
    smis = ['CC', 'CCC', 'CCOC', 'CCCCOC']
    inD = '\n'.join(smis)
    smiSup.SetData(inD, delimiter=",", smilesColumn=0, nameColumn=-1, titleLine=0)
    m = next(smiSup)
    m = smiSup[3]
    self.assertTrue(len(smiSup) == 4)

    with self.assertRaisesRegex(Exception, ""):
      smiSup[4]

    smiSup.SetData(inD, delimiter=",", smilesColumn=0, nameColumn=-1, titleLine=0)
    with self.assertRaisesRegex(Exception, ""):
      smiSup[4]

    sys.stderr.write(
      '>>> This may result in an infinite loop.  It should finish almost instantly\n')
    self.assertEqual(len(smiSup), 4)
    sys.stderr.write('<<< OK, it finished.\n')

  def test27SmilesWriter(self):
    fileN = os.path.join(RDConfig.RDBaseDir, 'Code', 'GraphMol', 'FileParsers', 'test_data',
                         'fewSmi.csv')
    #fileN = "../FileParsers/test_data/fewSmi.csv"

    smiSup = Chem.SmilesMolSupplier(fileN, delimiter=",", smilesColumn=1, nameColumn=0, titleLine=0)
    propNames = []
    propNames.append("Column_2")
    ofile = os.path.join(RDConfig.RDBaseDir, 'Code', 'GraphMol', 'Wrap', 'test_data',
                         'outSmiles.txt')
    writer = Chem.SmilesWriter(ofile)
    writer.SetProps(propNames)
    for mol in smiSup:
      writer.write(mol)
    writer.flush()

  def test28SmilesReverse(self):
    names = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10"]
    props = [
      "34.14", "25.78", "106.51", "82.78", "60.16", "87.74", "37.38", "77.28", "65.18", "0.00"
    ]
    ofile = os.path.join(RDConfig.RDBaseDir, 'Code', 'GraphMol', 'Wrap', 'test_data',
                         'outSmiles.txt')
    #ofile = "test_data/outSmiles.csv"
    smiSup = Chem.SmilesMolSupplier(ofile)
    i = 0
    for mol in smiSup:
      #print([repr(x) for x in mol.GetPropNames()])
      self.assertTrue(mol.GetProp("_Name") == names[i])
      self.assertTrue(mol.GetProp("Column_2") == props[i])
      i += 1

  def writerSDFile(self):
    fileN = os.path.join(RDConfig.RDBaseDir, 'Code', 'GraphMol', 'FileParsers', 'test_data',
                         'NCI_aids_few.sdf')
    #fileN = "../FileParsers/test_data/NCI_aids_few.sdf"
    ofile = os.path.join(RDConfig.RDBaseDir, 'Code', 'GraphMol', 'Wrap', 'test_data',
                         'outNCI_few.sdf')
    writer = Chem.SDWriter(ofile)
    sdSup = Chem.SDMolSupplier(fileN)
    for mol in sdSup:
      writer.write(mol)
    writer.flush()

  def test29SDWriterLoop(self):
    self.writerSDFile()
    fileN = os.path.join(RDConfig.RDBaseDir, 'Code', 'GraphMol', 'Wrap', 'test_data',
                         'outNCI_few.sdf')
    sdSup = Chem.SDMolSupplier(fileN)
    molNames = [
      "48", "78", "128", "163", "164", "170", "180", "186", "192", "203", "210", "211", "213",
      "220", "229", "256"
    ]
    chgs192 = {8: 1, 11: 1, 15: -1, 18: -1, 20: 1, 21: 1, 23: -1, 25: -1}
    i = 0

    for mol in sdSup:
      #print('mol:',mol)
      #print('\t',molNames[i])
      self.assertTrue(mol.GetProp("_Name") == molNames[i])
      i += 1
      if (mol.GetProp("_Name") == "192"):
        # test parsed charges on one of the molecules
        for id in chgs192.keys():
          self.assertTrue(mol.GetAtomWithIdx(id).GetFormalCharge() == chgs192[id])

  def test30Issues109and110(self):
    """ issues 110 and 109 were both related to handling of explicit Hs in
       SMILES input.

    """
    m1 = Chem.MolFromSmiles('N12[CH](SC(C)(C)[CH]1C(O)=O)[CH](C2=O)NC(=O)[CH](N)c3ccccc3')
    self.assertTrue(m1.GetNumAtoms() == 24)
    m2 = Chem.MolFromSmiles(
      'C1C=C([CH](N)C(=O)N[C]2([H])[C]3([H])SC(C)(C)[CH](C(=O)O)N3C(=O)2)C=CC=1')
    self.assertTrue(m2.GetNumAtoms() == 24)

    smi1 = Chem.MolToSmiles(m1)
    smi2 = Chem.MolToSmiles(m2)
    self.assertTrue(smi1 == smi2)

    m1 = Chem.MolFromSmiles('[H]CCl')
    self.assertTrue(m1.GetNumAtoms() == 2)
    self.assertTrue(m1.GetAtomWithIdx(0).GetNumExplicitHs() == 1)
    m1 = Chem.MolFromSmiles('[H][CH2]Cl')
    self.assertTrue(m1.GetNumAtoms() == 2)
    self.assertTrue(m1.GetAtomWithIdx(0).GetNumExplicitHs() == 3)
    m2 = Chem.AddHs(m1)
    self.assertTrue(m2.GetNumAtoms() == 5)
    m2 = Chem.RemoveHs(m2)
    self.assertTrue(m2.GetNumAtoms() == 2)

  def test31ChiralitySmiles(self):
    m1 = Chem.MolFromSmiles('F[C@](Br)(I)Cl')
    self.assertTrue(m1 is not None)
    self.assertTrue(m1.GetNumAtoms() == 5)
    self.assertTrue(Chem.MolToSmiles(m1, 1) == 'F[C@](Cl)(Br)I', Chem.MolToSmiles(m1, 1))

    m1 = Chem.MolFromSmiles('CC1C[C@@]1(Cl)F')
    self.assertTrue(m1 is not None)
    self.assertTrue(m1.GetNumAtoms() == 6)
    self.assertTrue(Chem.MolToSmiles(m1, 1) == 'CC1C[C@]1(F)Cl', Chem.MolToSmiles(m1, 1))

    m1 = Chem.MolFromSmiles('CC1C[C@]1(Cl)F')
    self.assertTrue(m1 is not None)
    self.assertTrue(m1.GetNumAtoms() == 6)
    self.assertTrue(Chem.MolToSmiles(m1, 1) == 'CC1C[C@@]1(F)Cl', Chem.MolToSmiles(m1, 1))

  def test31aChiralitySubstructs(self):
    m1 = Chem.MolFromSmiles('CC1C[C@@]1(Cl)F')
    self.assertTrue(m1 is not None)
    self.assertTrue(m1.GetNumAtoms() == 6)
    self.assertTrue(Chem.MolToSmiles(m1, 1) == 'CC1C[C@]1(F)Cl', Chem.MolToSmiles(m1, 1))

    m2 = Chem.MolFromSmiles('CC1C[C@]1(Cl)F')
    self.assertTrue(m2 is not None)
    self.assertTrue(m2.GetNumAtoms() == 6)
    self.assertTrue(Chem.MolToSmiles(m2, 1) == 'CC1C[C@@]1(F)Cl', Chem.MolToSmiles(m2, 1))

    self.assertTrue(m1.HasSubstructMatch(m1))
    self.assertTrue(m1.HasSubstructMatch(m2))
    self.assertTrue(m1.HasSubstructMatch(m1, useChirality=True))
    self.assertTrue(not m1.HasSubstructMatch(m2, useChirality=True))

  def _test32MolFilesWithChirality(self):
    inD = """chiral1.mol
  ChemDraw10160313232D

  5  4  0  0  0  0  0  0  0  0999 V2000
    0.0553    0.6188    0.0000 F   0  0  0  0  0  0  0  0  0  0  0  0
    0.0553   -0.2062    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
    0.7697   -0.6188    0.0000 I   0  0  0  0  0  0  0  0  0  0  0  0
   -0.6592   -0.6188    0.0000 Br  0  0  0  0  0  0  0  0  0  0  0  0
   -0.7697   -0.2062    0.0000 Cl  0  0  0  0  0  0  0  0  0  0  0  0
  1  2  1  0
  2  3  1  0
  2  4  1  1
  2  5  1  0
M  END
"""
    m1 = Chem.MolFromMolBlock(inD)
    self.assertTrue(m1 is not None)
    self.assertTrue(m1.GetNumAtoms() == 5)
    smi = Chem.MolToSmiles(m1)
    self.assertTrue(smi == 'F[C@](Cl)(Br)I', smi)

    inD = """chiral2.cdxml
  ChemDraw10160314052D

  5  4  0  0  0  0  0  0  0  0999 V2000
    0.0553    0.6188    0.0000 F   0  0  0  0  0  0  0  0  0  0  0  0
    0.0553   -0.2062    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
    0.7697   -0.6188    0.0000 I   0  0  0  0  0  0  0  0  0  0  0  0
   -0.6592   -0.6188    0.0000 Br  0  0  0  0  0  0  0  0  0  0  0  0
   -0.7697   -0.2062    0.0000 Cl  0  0  0  0  0  0  0  0  0  0  0  0
  1  2  1  0
  2  3  1  0
  2  4  1  6
  2  5  1  0
M  END
"""
    m1 = Chem.MolFromMolBlock(inD)
    self.assertTrue(m1 is not None)
    self.assertTrue(m1.GetNumAtoms() == 5)
    self.assertTrue(Chem.MolToSmiles(m1, 1) == 'F[C@@](Cl)(Br)I')

    inD = """chiral1.mol
  ChemDraw10160313232D

  5  4  0  0  0  0  0  0  0  0999 V2000
    0.0553    0.6188    0.0000 F   0  0  0  0  0  0  0  0  0  0  0  0
    0.0553   -0.2062    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
   -0.7697   -0.2062    0.0000 Cl  0  0  0  0  0  0  0  0  0  0  0  0
   -0.6592   -0.6188    0.0000 Br  0  0  0  0  0  0  0  0  0  0  0  0
    0.7697   -0.6188    0.0000 I   0  0  0  0  0  0  0  0  0  0  0  0
  1  2  1  0
  2  3  1  0
  2  4  1  1
  2  5  1  0
M  END
"""
    m1 = Chem.MolFromMolBlock(inD)
    self.assertTrue(m1 is not None)
    self.assertTrue(m1.GetNumAtoms() == 5)
    self.assertTrue(Chem.MolToSmiles(m1, 1) == 'F[C@](Cl)(Br)I')

    inD = """chiral1.mol
  ChemDraw10160313232D

  5  4  0  0  0  0  0  0  0  0999 V2000
    0.0553   -0.2062    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
   -0.7697   -0.2062    0.0000 Cl  0  0  0  0  0  0  0  0  0  0  0  0
   -0.6592   -0.6188    0.0000 Br  0  0  0  0  0  0  0  0  0  0  0  0
    0.7697   -0.6188    0.0000 I   0  0  0  0  0  0  0  0  0  0  0  0
    0.0553    0.6188    0.0000 F   0  0  0  0  0  0  0  0  0  0  0  0
  1  2  1  0
  1  3  1  1
  1  4  1  0
  1  5  1  0
M  END
"""
    m1 = Chem.MolFromMolBlock(inD)
    self.assertTrue(m1 is not None)
    self.assertTrue(m1.GetNumAtoms() == 5)
    self.assertTrue(Chem.MolToSmiles(m1, 1) == 'F[C@](Cl)(Br)I')

    inD = """chiral3.mol
  ChemDraw10160314362D

  4  3  0  0  0  0  0  0  0  0999 V2000
    0.4125    0.6188    0.0000 F   0  0  0  0  0  0  0  0  0  0  0  0
    0.4125   -0.2062    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
   -0.3020   -0.6188    0.0000 Br  0  0  0  0  0  0  0  0  0  0  0  0
   -0.4125   -0.2062    0.0000 Cl  0  0  0  0  0  0  0  0  0  0  0  0
  1  2  1  0
  2  3  1  1
  2  4  1  0
M  END

"""
    m1 = Chem.MolFromMolBlock(inD)
    self.assertTrue(m1 is not None)
    self.assertTrue(m1.GetNumAtoms() == 4)
    self.assertTrue(Chem.MolToSmiles(m1, 1) == 'F[C@H](Cl)Br')

    inD = """chiral4.mol
  ChemDraw10160314362D

  4  3  0  0  0  0  0  0  0  0999 V2000
    0.4125    0.6188    0.0000 F   0  0  0  0  0  0  0  0  0  0  0  0
    0.4125   -0.2062    0.0000 N   0  0  0  0  0  0  0  0  0  0  0  0
   -0.3020   -0.6188    0.0000 Br  0  0  0  0  0  0  0  0  0  0  0  0
   -0.4125   -0.2062    0.0000 Cl  0  0  0  0  0  0  0  0  0  0  0  0
  1  2  1  0
  2  3  1  1
  2  4  1  0
M  END

"""
    m1 = Chem.MolFromMolBlock(inD)
    self.assertTrue(m1 is not None)
    self.assertTrue(m1.GetNumAtoms() == 4)
    self.assertTrue(Chem.MolToSmiles(m1, 1) == 'FN(Cl)Br')

    inD = """chiral5.mol
  ChemDraw10160314362D

  4  3  0  0  0  0  0  0  0  0999 V2000
    0.4125    0.6188    0.0000 F   0  0  0  0  0  0  0  0  0  0  0  0
    0.4125   -0.2062    0.0000 N   0  0  0  0  0  0  0  0  0  0  0  0
   -0.3020   -0.6188    0.0000 Br  0  0  0  0  0  0  0  0  0  0  0  0
   -0.4125   -0.2062    0.0000 Cl  0  0  0  0  0  0  0  0  0  0  0  0
  1  2  1  0
  2  3  1  1
  2  4  1  0
M  CHG  1   2   1
M  END

"""
    m1 = Chem.MolFromMolBlock(inD)
    self.assertTrue(m1 is not None)
    self.assertTrue(m1.GetNumAtoms() == 4)
    self.assertTrue(Chem.MolToSmiles(m1, 1) == 'F[N@H+](Cl)Br')

    inD = """Case 10-14-3
  ChemDraw10140308512D

  4  3  0  0  0  0  0  0  0  0999 V2000
   -0.8250   -0.4125    0.0000 F   0  0  0  0  0  0  0  0  0  0  0  0
    0.0000   -0.4125    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
    0.8250   -0.4125    0.0000 Cl  0  0  0  0  0  0  0  0  0  0  0  0
    0.0000    0.4125    0.0000 Br  0  0  0  0  0  0  0  0  0  0  0  0
  1  2  1  0
  2  3  1  0
  2  4  1  1
M  END
"""
    m1 = Chem.MolFromMolBlock(inD)
    self.assertTrue(m1 is not None)
    self.assertTrue(m1.GetNumAtoms() == 4)
    self.assertTrue(Chem.MolToSmiles(m1, 1) == 'F[C@H](Cl)Br')

    inD = """Case 10-14-4
  ChemDraw10140308512D

  4  3  0  0  0  0  0  0  0  0999 V2000
   -0.8250   -0.4125    0.0000 F   0  0  0  0  0  0  0  0  0  0  0  0
    0.0000   -0.4125    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
    0.8250   -0.4125    0.0000 Cl  0  0  0  0  0  0  0  0  0  0  0  0
    0.0000    0.4125    0.0000 Br  0  0  0  0  0  0  0  0  0  0  0  0
  1  2  1  0
  2  3  1  1
  2  4  1  0
M  END

"""
    m1 = Chem.MolFromMolBlock(inD)
    self.assertTrue(m1 is not None)
    self.assertTrue(m1.GetNumAtoms() == 4)
    self.assertTrue(Chem.MolToSmiles(m1, 1) == 'F[C@H](Cl)Br')

    inD = """chiral4.mol
  ChemDraw10160315172D

  6  6  0  0  0  0  0  0  0  0999 V2000
   -0.4422    0.1402    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
   -0.4422   -0.6848    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
    0.2723   -0.2723    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
   -0.8547    0.8547    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
    0.6848    0.4422    0.0000 F   0  0  0  0  0  0  0  0  0  0  0  0
    0.8547   -0.8547    0.0000 Cl  0  0  0  0  0  0  0  0  0  0  0  0
  1  2  1  0
  2  3  1  0
  3  1  1  0
  1  4  1  0
  3  5  1  1
  3  6  1  0
M  END
"""
    m1 = Chem.MolFromMolBlock(inD)
    self.assertTrue(m1 is not None)
    self.assertTrue(m1.GetNumAtoms() == 6)
    self.assertTrue(Chem.MolToSmiles(m1, 1) == 'CC1C[C@@]1(F)Cl', Chem.MolToSmiles(m1, 1))

    inD = """chiral4.mol
  ChemDraw10160315172D

  6  6  0  0  0  0  0  0  0  0999 V2000
   -0.4422    0.1402    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
   -0.4422   -0.6848    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
    0.2723   -0.2723    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
   -0.8547    0.8547    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
    0.6848    0.4422    0.0000 F   0  0  0  0  0  0  0  0  0  0  0  0
    0.8547   -0.8547    0.0000 Cl  0  0  0  0  0  0  0  0  0  0  0  0
  1  2  1  0
  2  3  1  0
  3  1  1  0
  1  4  1  0
  3  5  1  6
  3  6  1  0
M  END
"""
    m1 = Chem.MolFromMolBlock(inD)
    self.assertTrue(m1 is not None)
    self.assertTrue(m1.GetNumAtoms() == 6)
    self.assertTrue(Chem.MolToSmiles(m1, 1) == 'CC1C[C@]1(F)Cl', Chem.MolToSmiles(m1, 1))

  def test33Issue65(self):
    """ issue 65 relates to handling of [H] in SMARTS

    """
    m1 = Chem.MolFromSmiles('OC(O)(O)O')
    m2 = Chem.MolFromSmiles('OC(O)O')
    m3 = Chem.MolFromSmiles('OCO')
    q1 = Chem.MolFromSmarts('OC[H]', 1)
    q2 = Chem.MolFromSmarts('O[C;H1]', 1)
    q3 = Chem.MolFromSmarts('O[C;H1][H]', 1)

    self.assertTrue(not m1.HasSubstructMatch(q1))
    self.assertTrue(not m1.HasSubstructMatch(q2))
    self.assertTrue(not m1.HasSubstructMatch(q3))

    self.assertTrue(m2.HasSubstructMatch(q1))
    self.assertTrue(m2.HasSubstructMatch(q2))
    self.assertTrue(m2.HasSubstructMatch(q3))

    self.assertTrue(m3.HasSubstructMatch(q1))
    self.assertTrue(not m3.HasSubstructMatch(q2))
    self.assertTrue(not m3.HasSubstructMatch(q3))

    m1H = Chem.AddHs(m1)
    m2H = Chem.AddHs(m2)
    m3H = Chem.AddHs(m3)
    q1 = Chem.MolFromSmarts('OC[H]')
    q2 = Chem.MolFromSmarts('O[C;H1]')
    q3 = Chem.MolFromSmarts('O[C;H1][H]')

    self.assertTrue(not m1H.HasSubstructMatch(q1))
    self.assertTrue(not m1H.HasSubstructMatch(q2))
    self.assertTrue(not m1H.HasSubstructMatch(q3))

    #m2H.Debug()
    self.assertTrue(m2H.HasSubstructMatch(q1))
    self.assertTrue(m2H.HasSubstructMatch(q2))
    self.assertTrue(m2H.HasSubstructMatch(q3))

    self.assertTrue(m3H.HasSubstructMatch(q1))
    self.assertTrue(not m3H.HasSubstructMatch(q2))
    self.assertTrue(not m3H.HasSubstructMatch(q3))

  def test34Issue124(self):
    """ issue 124 relates to calculation of the distance matrix

    """
    m = Chem.MolFromSmiles('CC=C')
    d = Chem.GetDistanceMatrix(m, 0)
    self.assertTrue(feq(d[0, 1], 1.0))
    self.assertTrue(feq(d[0, 2], 2.0))
    # force an update:
    d = Chem.GetDistanceMatrix(m, 1, 0, 1)
    self.assertTrue(feq(d[0, 1], 1.0))
    self.assertTrue(feq(d[0, 2], 1.5))

  def test35ChiralityPerception(self):
    """ Test perception of chirality and CIP encoding
    """
    m = Chem.MolFromSmiles('F[C@]([C@])(Cl)Br')
    Chem.AssignStereochemistry(m, 1)
    self.assertTrue(m.GetAtomWithIdx(1).HasProp('_CIPCode'))
    self.assertFalse(m.GetAtomWithIdx(2).HasProp('_CIPCode'))
    Chem.RemoveStereochemistry(m)
    self.assertFalse(m.GetAtomWithIdx(1).HasProp('_CIPCode'))

    m = Chem.MolFromSmiles('F[C@H](C)C')
    Chem.AssignStereochemistry(m, 1)
    self.assertTrue(m.GetAtomWithIdx(1).GetChiralTag() == Chem.ChiralType.CHI_UNSPECIFIED)
    self.assertFalse(m.GetAtomWithIdx(1).HasProp('_CIPCode'))

    m = Chem.MolFromSmiles('F\\C=C/Cl')
    self.assertTrue(m.GetBondWithIdx(0).GetStereo() == Chem.BondStereo.STEREONONE)
    self.assertTrue(m.GetBondWithIdx(1).GetStereo() == Chem.BondStereo.STEREOZ)
    atoms = m.GetBondWithIdx(1).GetStereoAtoms()
    self.assertTrue(0 in atoms)
    self.assertTrue(3 in atoms)
    self.assertTrue(m.GetBondWithIdx(2).GetStereo() == Chem.BondStereo.STEREONONE)
    Chem.RemoveStereochemistry(m)
    self.assertTrue(m.GetBondWithIdx(1).GetStereo() == Chem.BondStereo.STEREONONE)

    m = Chem.MolFromSmiles('F\\C=CCl')
    self.assertTrue(m.GetBondWithIdx(1).GetStereo() == Chem.BondStereo.STEREONONE)

  def checkDefaultBondProperties(self, m):
    for bond in m.GetBonds():
      self.assertIn(bond.GetBondType(), [Chem.BondType.SINGLE, Chem.BondType.DOUBLE])
      self.assertEqual(bond.GetBondDir(), Chem.BondDir.NONE)
      self.assertEqual(list(bond.GetStereoAtoms()), [])
      self.assertEqual(bond.GetStereo(), Chem.BondStereo.STEREONONE)

  def assertHasDoubleBondStereo(self, smi):
    m = Chem.MolFromSmiles(smi)

    self.checkDefaultBondProperties(m)

    Chem.FindPotentialStereoBonds(m)

    for bond in m.GetBonds():
      self.assertIn(bond.GetBondType(), [Chem.BondType.SINGLE, Chem.BondType.DOUBLE])
      self.assertEqual(bond.GetBondDir(), Chem.BondDir.NONE)

      if bond.GetBondType() == Chem.BondType.DOUBLE:
        self.assertEqual(bond.GetStereo(), Chem.BondStereo.STEREOANY)
        self.assertEqual(len(list(bond.GetStereoAtoms())), 2)
      else:
        self.assertEqual(list(bond.GetStereoAtoms()), [])
        self.assertEqual(bond.GetStereo(), Chem.BondStereo.STEREONONE)

  def testFindPotentialStereoBonds(self):
    self.assertHasDoubleBondStereo("FC=CF")
    self.assertHasDoubleBondStereo("FC(Cl)=C(Br)I")
    self.assertHasDoubleBondStereo("FC=CC=CC=CCl")
    self.assertHasDoubleBondStereo("C1CCCCC1C=CC1CCCCC1")

  def assertDoesNotHaveDoubleBondStereo(self, smi):
    m = Chem.MolFromSmiles(smi)
    self.checkDefaultBondProperties(m)
    Chem.FindPotentialStereoBonds(m)
    self.checkDefaultBondProperties(m)

  def testFindPotentialStereoBondsShouldNotFindThisDoubleBondAsStereo(self):
    self.assertDoesNotHaveDoubleBondStereo("FC(F)=CF")
    self.assertDoesNotHaveDoubleBondStereo("C=C")
    self.assertDoesNotHaveDoubleBondStereo("C1CCCCC1C(C1CCCCC1)=CC1CCCCC1")

  def assertDoubleBondStereo(self, smi, stereo):
    mol = Chem.MolFromSmiles(smi)

    bond = mol.GetBondWithIdx(1)
    self.assertEqual(bond.GetBondType(), Chem.BondType.DOUBLE)
    self.assertEqual(bond.GetStereo(), stereo)
    self.assertEqual(list(bond.GetStereoAtoms()), [0, 3])

  def allStereoBonds(self, bonds):
    for bond in bonds:
      self.assertEqual(len(list(bond.GetStereoAtoms())), 2)

  def testBondSetStereo(self):
    for testAssignStereo in [False, True]:
      mol = Chem.MolFromSmiles("FC=CF")
      Chem.FindPotentialStereoBonds(mol)

      for bond in mol.GetBonds():
        if (bond.GetBondType() == Chem.BondType.DOUBLE
            and bond.GetStereo() == Chem.BondStereo.STEREOANY):
          break
      self.assertEqual(bond.GetBondType(), Chem.BondType.DOUBLE)
      self.assertEqual(bond.GetStereo(), Chem.BondStereo.STEREOANY)
      self.assertEqual(list(bond.GetStereoAtoms()), [0, 3])

      bond.SetStereo(Chem.BondStereo.STEREOTRANS)
      self.assertEqual(bond.GetStereo(), Chem.BondStereo.STEREOTRANS)
      if testAssignStereo:  # should be invariant of Chem.AssignStereochemistry being called
        Chem.AssignStereochemistry(mol, force=True)
      smi = Chem.MolToSmiles(mol, isomericSmiles=True)
      self.allStereoBonds([bond])
      self.assertEqual(smi, "F/C=C/F")
      self.assertDoubleBondStereo(smi, Chem.BondStereo.STEREOE)

      bond.SetStereo(Chem.BondStereo.STEREOCIS)
      self.assertEqual(bond.GetStereo(), Chem.BondStereo.STEREOCIS)
      if testAssignStereo:
        Chem.AssignStereochemistry(mol, force=True)
      smi = Chem.MolToSmiles(mol, isomericSmiles=True)
      self.allStereoBonds([bond])
      self.assertEqual(smi, r"F/C=C\F")
      self.assertDoubleBondStereo(smi, Chem.BondStereo.STEREOZ)

  def recursive_enumerate_stereo_bonds(self, mol, done_bonds, bonds):
    if not bonds:
      yield done_bonds, Chem.Mol(mol)
      return

    bond = bonds[0]
    child_bonds = bonds[1:]
    self.assertEqual(len(list(bond.GetStereoAtoms())), 2)
    bond.SetStereo(Chem.BondStereo.STEREOTRANS)
    for isomer in self.recursive_enumerate_stereo_bonds(mol, done_bonds + [Chem.BondStereo.STEREOE],
                                                        child_bonds):
      yield isomer

    self.assertEqual(len(list(bond.GetStereoAtoms())), 2)
    bond.SetStereo(Chem.BondStereo.STEREOCIS)
    for isomer in self.recursive_enumerate_stereo_bonds(mol, done_bonds + [Chem.BondStereo.STEREOZ],
                                                        child_bonds):
      yield isomer

  def testBondSetStereoDifficultCase(self):
    unspec_smiles = "CCC=CC(CO)=C(C)CC"
    mol = Chem.MolFromSmiles(unspec_smiles)
    Chem.FindPotentialStereoBonds(mol)

    stereo_bonds = []
    for bond in mol.GetBonds():
      if bond.GetStereo() == Chem.BondStereo.STEREOANY:
        stereo_bonds.append(bond)

    isomers = set()
    for bond_stereo, isomer in self.recursive_enumerate_stereo_bonds(mol, [], stereo_bonds):
      self.allStereoBonds(stereo_bonds)
      isosmi = Chem.MolToSmiles(isomer, isomericSmiles=True)
      self.allStereoBonds(stereo_bonds)

      self.assertNotIn(isosmi, isomers)
      isomers.add(isosmi)

      isomol = Chem.MolFromSmiles(isosmi)
      round_trip_stereo = [
        b.GetStereo() for b in isomol.GetBonds() if b.GetStereo() != Chem.BondStereo.STEREONONE
      ]

      self.assertEqual(bond_stereo, round_trip_stereo)

    self.assertEqual(len(isomers), 4)

  def getNumUnspecifiedBondStereo(self, smi):
    mol = Chem.MolFromSmiles(smi)
    Chem.FindPotentialStereoBonds(mol)

    count = 0
    for bond in mol.GetBonds():
      if bond.GetStereo() == Chem.BondStereo.STEREOANY:
        count += 1

    return count

  def testBondSetStereoReallyDifficultCase(self):
    # this one is much trickier because a double bond can gain and
    # lose it's stereochemistry based upon whether 2 other double
    # bonds have the same or different stereo chemistry.

    unspec_smiles = "CCC=CC(C=CCC)=C(CO)CC"
    mol = Chem.MolFromSmiles(unspec_smiles)
    Chem.FindPotentialStereoBonds(mol)

    stereo_bonds = []
    for bond in mol.GetBonds():
      if bond.GetStereo() == Chem.BondStereo.STEREOANY:
        stereo_bonds.append(bond)

    self.assertEqual(len(stereo_bonds), 2)

    isomers = set()
    for bond_stereo, isomer in self.recursive_enumerate_stereo_bonds(mol, [], stereo_bonds):
      isosmi = Chem.MolToSmiles(isomer, isomericSmiles=True)
      isomers.add(isosmi)

    self.assertEqual(len(isomers), 3)

    # one of these then gains a new stereo bond due to the
    # introduction of a new symmetry
    counts = {}
    for isosmi in isomers:
      num_unspecified = self.getNumUnspecifiedBondStereo(isosmi)
      counts[num_unspecified] = counts.get(num_unspecified, 0) + 1

    # 2 of the isomers don't have any unspecified bond stereo centers
    # left, 1 does
    self.assertEqual(counts, {0: 2, 1: 1})

  def assertBondSetStereoIsAlwaysEquivalent(self, all_smiles, desired_stereo, bond_idx):
    refSmiles = None
    for smi in all_smiles:
      mol = Chem.MolFromSmiles(smi)

      doubleBond = None
      for bond in mol.GetBonds():
        if bond.GetBondType() == Chem.BondType.DOUBLE:
          doubleBond = bond

      self.assertTrue(doubleBond is not None)

      Chem.FindPotentialStereoBonds(mol)
      doubleBond.SetStereo(desired_stereo)

      isosmi = Chem.MolToSmiles(mol, isomericSmiles=True)

      if refSmiles is None:
        refSmiles = isosmi

      self.assertEqual(refSmiles, isosmi)

  def testBondSetStereoAllHalogens(self):
    # can't get much more brutal than this test
    from itertools import combinations, permutations
    halogens = ['F', 'Cl', 'Br', 'I']

    # binary double bond stereo
    for unique_set in combinations(halogens, 2):
      all_smiles = []
      for fmt in ['%sC=C%s', 'C(%s)=C%s']:
        for ordering in permutations(unique_set):
          all_smiles.append(fmt % ordering)

      #print(fmt, all_smiles)
      for desired_stereo in [Chem.BondStereo.STEREOTRANS, Chem.BondStereo.STEREOCIS]:
        self.assertBondSetStereoIsAlwaysEquivalent(all_smiles, desired_stereo, 1)

    # tertiary double bond stereo
    for unique_set in combinations(halogens, 3):
      for mono_side in unique_set:
        halogens_left = list(unique_set)
        halogens_left.remove(mono_side)
        for binary_side in combinations(halogens_left, 2):
          all_smiles = []

          for binary_side_permutation in permutations(binary_side):
            all_smiles.append('%sC=C(%s)%s' % ((mono_side, ) + binary_side_permutation))
            all_smiles.append('C(%s)=C(%s)%s' % ((mono_side, ) + binary_side_permutation))

            all_smiles.append('%sC(%s)=C%s' % (binary_side_permutation + (mono_side, )))
            all_smiles.append('C(%s)(%s)=C%s' % (binary_side_permutation + (mono_side, )))

          #print(all_smiles)
          for desired_stereo in [Chem.BondStereo.STEREOTRANS, Chem.BondStereo.STEREOCIS]:
            self.assertBondSetStereoIsAlwaysEquivalent(all_smiles, desired_stereo, 1)

    # quaternary double bond stereo
    for unique_ordering in permutations(halogens):
      left_side = unique_ordering[:2]
      rght_side = unique_ordering[2:]

      all_smiles = []
      for left_side_permutation in permutations(left_side):
        for rght_side_permutation in permutations(rght_side):
          for smifmt in ['%sC(%s)=C(%s)%s', 'C(%s)(%s)=C(%s)%s']:
            all_smiles.append(smifmt % (left_side_permutation + rght_side_permutation))

      #print(all_smiles)
      for desired_stereo in [Chem.BondStereo.STEREOTRANS, Chem.BondStereo.STEREOCIS]:
        self.assertBondSetStereoIsAlwaysEquivalent(all_smiles, desired_stereo, 1)

  def testBondSetStereoAtoms(self):
    # use this difficult molecule that only generates 4 isomers, but
    # assume all double bonds are stereo!
    unspec_smiles = "CCC=CC(C=CCC)=C(CO)CC"
    mol = Chem.MolFromSmiles(unspec_smiles)

    def getNbr(atom, exclude):
      for nbr in atom.GetNeighbors():
        if nbr.GetIdx() not in exclude:
          return nbr
      raise ValueError("No neighbor found!")

    double_bonds = []
    for bond in mol.GetBonds():
      if bond.GetBondType() == 2:
        double_bonds.append(bond)

        exclude = {bond.GetBeginAtomIdx(), bond.GetEndAtomIdx()}
        bgnNbr = getNbr(bond.GetBeginAtom(), exclude)
        endNbr = getNbr(bond.GetEndAtom(), exclude)

        bond.SetStereoAtoms(bgnNbr.GetIdx(), endNbr.GetIdx())

    self.assertEqual(len(double_bonds), 3)

    import itertools
    stereos = [Chem.BondStereo.STEREOE, Chem.BondStereo.STEREOZ]
    isomers = set()
    for stereo_config in itertools.product(stereos, repeat=len(double_bonds)):
      for bond, stereo in zip(double_bonds, stereo_config):
        bond.SetStereo(stereo)
      smi = Chem.MolToSmiles(mol, True)
      isomers.add(smi)

    # the dependent double bond stereo isn't picked up by this, should it?
    self.assertEqual(len(isomers), 6)

    # round tripping them through one more time does pick up the dependency, so meh?
    round_trip_isomers = set()
    for smi in isomers:
      isosmi = Chem.MolToSmiles(Chem.MolFromSmiles(smi), True)
      round_trip_isomers.add(isosmi)

    self.assertEqual(len(round_trip_isomers), 4)

  def test36SubstructMatchStr(self):
    """ test the _SubstructMatchStr function """
    query = Chem.MolFromSmarts('[n,p]1ccccc1')
    self.assertTrue(query)
    mol = Chem.MolFromSmiles('N1=CC=CC=C1')
    self.assertTrue(mol.HasSubstructMatch(query))
    self.assertTrue(Chem._HasSubstructMatchStr(mol.ToBinary(), query))
    mol = Chem.MolFromSmiles('S1=CC=CC=C1')
    self.assertTrue(not Chem._HasSubstructMatchStr(mol.ToBinary(), query))
    self.assertTrue(not mol.HasSubstructMatch(query))
    mol = Chem.MolFromSmiles('P1=CC=CC=C1')
    self.assertTrue(mol.HasSubstructMatch(query))
    self.assertTrue(Chem._HasSubstructMatchStr(mol.ToBinary(), query))

  def test37SanitException(self):
    mol = Chem.MolFromSmiles('CC(C)(C)(C)C', 0)
    self.assertTrue(mol)
    self.assertRaises(ValueError, lambda: Chem.SanitizeMol(mol))

  def test38TDTSuppliers(self):
    data = """$SMI<Cc1nnc(N)nc1C>
CAS<17584-12-2>
|
$SMI<Cc1n[nH]c(=O)nc1N>
CAS<~>
|
$SMI<Cc1n[nH]c(=O)[nH]c1=O>
CAS<932-53-6>
|
$SMI<Cc1nnc(NN)nc1O>
CAS<~>
|"""
    suppl = Chem.TDTMolSupplier()
    suppl.SetData(data, "CAS")
    i = 0
    for mol in suppl:
      self.assertTrue(mol)
      self.assertTrue(mol.GetNumAtoms())
      self.assertTrue(mol.HasProp("CAS"))
      self.assertTrue(mol.HasProp("_Name"))
      self.assertTrue(mol.GetProp("CAS") == mol.GetProp("_Name"))
      self.assertTrue(mol.GetNumConformers() == 0)
      i += 1
    self.assertTrue(i == 4)
    self.assertTrue(len(suppl) == 4)

  def test38Issue266(self):
    """ test issue 266: generation of kekulized smiles"""
    mol = Chem.MolFromSmiles('c1ccccc1')
    Chem.Kekulize(mol)
    smi = Chem.MolToSmiles(mol)
    self.assertTrue(smi == 'c1ccccc1')
    smi = Chem.MolToSmiles(mol, kekuleSmiles=True)
    self.assertTrue(smi == 'C1=CC=CC=C1')

  def test39Issue273(self):
    """ test issue 273: MolFileComments and MolFileInfo props ending up in SD files

    """
    fileN = os.path.join(RDConfig.RDBaseDir, 'Code', 'GraphMol', 'Wrap', 'test_data',
                         'outNCI_few.sdf')
    suppl = Chem.SDMolSupplier(fileN)
    ms = [x for x in suppl]
    for m in ms:
      self.assertTrue(m.HasProp('_MolFileInfo'))
      self.assertTrue(m.HasProp('_MolFileComments'))
    fName = tempfile.NamedTemporaryFile(suffix='.sdf', delete=False).name
    w = Chem.SDWriter(fName)
    w.SetProps(ms[0].GetPropNames())
    for m in ms:
      w.write(m)
    w = None

    with open(fName, 'r') as txtFile:
      txt = txtFile.read()
    os.unlink(fName)
    self.assertTrue(txt.find('MolFileInfo') == -1)
    self.assertTrue(txt.find('MolFileComments') == -1)

  def test40SmilesRootedAtAtom(self):
    """ test the rootAtAtom functionality

    """
    smi = 'CN(C)C'
    m = Chem.MolFromSmiles(smi)

    self.assertTrue(Chem.MolToSmiles(m) == 'CN(C)C')
    self.assertTrue(Chem.MolToSmiles(m, rootedAtAtom=1) == 'N(C)(C)C')

  def test41SetStreamIndices(self):
    fileN = os.path.join(RDConfig.RDBaseDir, 'Code', 'GraphMol', 'FileParsers', 'test_data',
                         'NCI_aids_few.sdf')
    allIndices = []
    ifs = open(fileN, 'rb')
    addIndex = True
    line = True
    pos = 0
    while (line):
      if (addIndex):
        pos = ifs.tell()
      line = ifs.readline().decode('utf-8')
      if (line):
        if (addIndex):
          allIndices.append(pos)
        addIndex = (line[:4] == '$$$$')
    ifs.close()
    indices = allIndices
    sdSup = Chem.SDMolSupplier(fileN)
    molNames = [
      "48", "78", "128", "163", "164", "170", "180", "186", "192", "203", "210", "211", "213",
      "220", "229", "256"
    ]

    sdSup._SetStreamIndices(indices)
    self.assertTrue(len(sdSup) == 16)
    mol = sdSup[5]
    self.assertTrue(mol.GetProp("_Name") == "170")

    i = 0
    for mol in sdSup:
      self.assertTrue(mol)
      self.assertTrue(mol.GetProp("_Name") == molNames[i])
      i += 1

    ns = [mol.GetProp("_Name") for mol in sdSup]
    self.assertTrue(ns == molNames)

    # this can also be used to skip molecules in the file:
    indices = [allIndices[0], allIndices[2], allIndices[5]]
    sdSup._SetStreamIndices(indices)
    self.assertTrue(len(sdSup) == 3)
    mol = sdSup[2]
    self.assertTrue(mol.GetProp("_Name") == "170")

    # or to reorder them:
    indices = [allIndices[0], allIndices[5], allIndices[2]]
    sdSup._SetStreamIndices(indices)
    self.assertTrue(len(sdSup) == 3)
    mol = sdSup[1]
    self.assertTrue(mol.GetProp("_Name") == "170")

  def test42LifeTheUniverseAndEverything(self):
    self.assertTrue(True)

  def test43TplFileParsing(self):
    fileN = os.path.join(RDConfig.RDBaseDir, 'Code', 'GraphMol', 'FileParsers', 'test_data',
                         'cmpd2.tpl')
    m1 = Chem.MolFromTPLFile(fileN)
    self.assertTrue(m1 is not None)
    self.assertTrue(m1.GetNumAtoms() == 12)
    self.assertTrue(m1.GetNumConformers() == 2)

    m1 = Chem.MolFromTPLFile(fileN, skipFirstConf=True)
    self.assertTrue(m1 is not None)
    self.assertTrue(m1.GetNumAtoms() == 12)
    self.assertTrue(m1.GetNumConformers() == 1)

    with open(fileN, 'r') as blockFile:
      block = blockFile.read()
    m1 = Chem.MolFromTPLBlock(block)
    self.assertTrue(m1 is not None)
    self.assertTrue(m1.GetNumAtoms() == 12)
    self.assertTrue(m1.GetNumConformers() == 2)

  def test44TplFileWriting(self):
    fileN = os.path.join(RDConfig.RDBaseDir, 'Code', 'GraphMol', 'FileParsers', 'test_data',
                         'cmpd2.tpl')
    m1 = Chem.MolFromTPLFile(fileN)
    self.assertTrue(m1 is not None)
    self.assertTrue(m1.GetNumAtoms() == 12)
    self.assertTrue(m1.GetNumConformers() == 2)

    block = Chem.MolToTPLBlock(m1)
    m1 = Chem.MolFromTPLBlock(block)
    self.assertTrue(m1 is not None)
    self.assertTrue(m1.GetNumAtoms() == 12)
    self.assertTrue(m1.GetNumConformers() == 2)

  def test45RingInfo(self):
    """ test the RingInfo class

    """
    smi = 'CNC'
    m = Chem.MolFromSmiles(smi)
    ri = m.GetRingInfo()
    self.assertTrue(ri)
    self.assertTrue(ri.NumRings() == 0)
    self.assertFalse(ri.IsAtomInRingOfSize(0, 3))
    self.assertFalse(ri.IsAtomInRingOfSize(1, 3))
    self.assertFalse(ri.IsAtomInRingOfSize(2, 3))
    self.assertFalse(ri.IsBondInRingOfSize(1, 3))
    self.assertFalse(ri.IsBondInRingOfSize(2, 3))
    if hasattr(Chem, 'FindRingFamilies'):
      self.assertEqual(ri.AtomRingFamilies(), ())
    if hasattr(Chem, 'FindRingFamilies'):
      self.assertEqual(ri.BondRingFamilies(), ())

    smi = 'C1CC2C1C2'
    m = Chem.MolFromSmiles(smi)
    ri = m.GetRingInfo()
    self.assertTrue(ri)
    self.assertEqual(ri.NumRings(), 2)
    self.assertFalse(ri.IsAtomInRingOfSize(0, 3))
    self.assertTrue(ri.IsAtomInRingOfSize(0, 4))
    self.assertFalse(ri.IsBondInRingOfSize(0, 3))
    self.assertTrue(ri.IsBondInRingOfSize(0, 4))
    self.assertTrue(ri.IsAtomInRingOfSize(2, 4))
    self.assertTrue(ri.IsAtomInRingOfSize(2, 3))
    self.assertTrue(ri.IsBondInRingOfSize(2, 3))
    self.assertTrue(ri.IsBondInRingOfSize(2, 4))
    self.assertEqual(ri.AtomRings(), ((0, 3, 2, 1), (4, 3, 2)))
    self.assertEqual(ri.BondRings(), ((4, 2, 1, 0), (3, 2, 5)))
    self.assertEqual(len(ri.AtomMembers(2)), 2)
    self.assertEqual(ri.AtomRingSizes(2), (4, 3))
    self.assertEqual(ri.AtomRingSizes(99), ())
    self.assertTrue(ri.AreAtomsInSameRing(2, 3))
    self.assertFalse(ri.AreAtomsInSameRing(1, 4))
    self.assertTrue(ri.AreAtomsInSameRingOfSize(2, 3, 3))
    self.assertTrue(ri.AreAtomsInSameRingOfSize(2, 3, 4))
    self.assertFalse(ri.AreAtomsInSameRingOfSize(2, 3, 5))
    self.assertEqual(len(ri.BondMembers(2)), 2)
    self.assertEqual(len(ri.BondMembers(0)), 1)
    self.assertTrue(ri.IsRingFused(0))
    self.assertTrue(ri.IsRingFused(1))
    self.assertTrue(ri.AreRingsFused(0, 1))
    self.assertTrue(ri.NumFusedBonds(0) == 1)
    self.assertTrue(ri.NumFusedBonds(1) == 1)
    self.assertEqual(ri.BondRingSizes(2), (4, 3))
    self.assertEqual(ri.BondRingSizes(0), (4, ))
    self.assertEqual(ri.BondRingSizes(99), ())
    self.assertTrue(ri.AreBondsInSameRing(1, 2))
    self.assertTrue(ri.AreBondsInSameRing(2, 5))
    self.assertFalse(ri.AreBondsInSameRing(1, 3))
    self.assertTrue(ri.AreBondsInSameRingOfSize(1, 2, 4))
    self.assertTrue(ri.AreBondsInSameRingOfSize(2, 5, 3))
    self.assertFalse(ri.AreBondsInSameRingOfSize(1, 2, 3))
    self.assertFalse(ri.AreBondsInSameRingOfSize(1, 3, 4))

    if hasattr(Chem, 'FindRingFamilies'):
      ri = m.GetRingInfo()
      self.assertFalse(ri.AreRingFamiliesInitialized())
      Chem.FindRingFamilies(m)
      ri = m.GetRingInfo()
      self.assertTrue(ri.AreRingFamiliesInitialized())
      self.assertEqual(ri.NumRingFamilies(), 2)
      self.assertEqual(sorted(ri.AtomRingFamilies()), [(0, 1, 2, 3), (2, 3, 4)])
      self.assertEqual(sorted(ri.BondRingFamilies()), [(0, 1, 2, 4), (2, 3, 5)])

  def test46ReplaceCore(self):
    """ test the ReplaceCore functionality

    """

    core = Chem.MolFromSmiles('C=O')

    smi = 'CCC=O'
    m = Chem.MolFromSmiles(smi)
    r = Chem.ReplaceCore(m, core)
    self.assertTrue(r)
    self.assertEqual(Chem.MolToSmiles(r, True), '[1*]CC')

    smi = 'C1CC(=O)CC1'
    m = Chem.MolFromSmiles(smi)
    r = Chem.ReplaceCore(m, core)
    self.assertTrue(r)
    self.assertEqual(Chem.MolToSmiles(r, True), '[1*]CCCC[2*]')

    smi = 'C1CC(=N)CC1'
    m = Chem.MolFromSmiles(smi)
    r = Chem.ReplaceCore(m, core)
    self.assertFalse(r)

    # smiles, smarts, replaceDummies, labelByIndex, useChirality
    expected = {
      ('C1O[C@@]1(OC)NC', 'C1O[C@]1(*)*', False, False, False): '[1*]OC.[2*]NC',
      ('C1O[C@@]1(OC)NC', 'C1O[C@]1(*)*', False, False, True): '[1*]NC.[2*]OC',
      ('C1O[C@@]1(OC)NC', 'C1O[C@]1(*)*', False, True, False): '[3*]OC.[4*]NC',
      ('C1O[C@@]1(OC)NC', 'C1O[C@]1(*)*', False, True, True): '[3*]NC.[4*]OC',
      ('C1O[C@@]1(OC)NC', 'C1O[C@]1(*)*', True, False, False): '[1*]C.[2*]C',
      ('C1O[C@@]1(OC)NC', 'C1O[C@]1(*)*', True, False, True): '[1*]C.[2*]C',
      ('C1O[C@@]1(OC)NC', 'C1O[C@]1(*)*', True, True, False): '[3*]C.[4*]C',
      ('C1O[C@@]1(OC)NC', 'C1O[C@]1(*)*', True, True, True): '[3*]C.[4*]C',
      ('C1O[C@]1(OC)NC', 'C1O[C@]1(*)*', False, False, False): '[1*]OC.[2*]NC',
      ('C1O[C@]1(OC)NC', 'C1O[C@]1(*)*', False, False, True): '[1*]OC.[2*]NC',
      ('C1O[C@]1(OC)NC', 'C1O[C@]1(*)*', False, True, False): '[3*]OC.[4*]NC',
      ('C1O[C@]1(OC)NC', 'C1O[C@]1(*)*', False, True, True): '[3*]OC.[4*]NC',
      ('C1O[C@]1(OC)NC', 'C1O[C@]1(*)*', True, False, False): '[1*]C.[2*]C',
      ('C1O[C@]1(OC)NC', 'C1O[C@]1(*)*', True, False, True): '[1*]C.[2*]C',
      ('C1O[C@]1(OC)NC', 'C1O[C@]1(*)*', True, True, False): '[3*]C.[4*]C',
      ('C1O[C@]1(OC)NC', 'C1O[C@]1(*)*', True, True, True): '[3*]C.[4*]C',
    }

    for (smiles, smarts, replaceDummies, labelByIndex,
         useChirality), expected_smiles in expected.items():
      mol = Chem.MolFromSmiles(smiles)
      core = Chem.MolFromSmarts(smarts)
      nm = Chem.ReplaceCore(mol, core, replaceDummies=replaceDummies, labelByIndex=labelByIndex,
                            useChirality=useChirality)

      if Chem.MolToSmiles(nm, True) != expected_smiles:
        print(
          "ReplaceCore(%r, %r, replaceDummies=%r, labelByIndex=%r, useChirality=%r" %
          (smiles, smarts, replaceDummies, labelByIndex, useChirality), file=sys.stderr)
        print("expected: %s\ngot: %s" % (expected_smiles, Chem.MolToSmiles(nm, True)),
              file=sys.stderr)
        self.assertEqual(expected_smiles, Chem.MolToSmiles(nm, True))

      matchVect = mol.GetSubstructMatch(core, useChirality=useChirality)
      nm = Chem.ReplaceCore(mol, core, matchVect, replaceDummies=replaceDummies,
                            labelByIndex=labelByIndex)
      if Chem.MolToSmiles(nm, True) != expected_smiles:
        print(
          "ReplaceCore(%r, %r, %r, replaceDummies=%r, labelByIndex=%rr" %
          (smiles, smarts, matchVect, replaceDummies, labelByIndex), file=sys.stderr)
        print("expected: %s\ngot: %s" % (expected_smiles, Chem.MolToSmiles(nm, True)),
              file=sys.stderr)
        self.assertEqual(expected_smiles, Chem.MolToSmiles(nm, True))

    mol = Chem.MolFromSmiles("C")
    smarts = Chem.MolFromSmarts("C")
    try:
      Chem.ReplaceCore(mol, smarts, (3, ))
      self.asssertFalse(True)
    except Exception:
      pass

    mol = Chem.MolFromSmiles("C")
    smarts = Chem.MolFromSmarts("C")
    try:
      Chem.ReplaceCore(mol, smarts, (0, 0))
      self.asssertFalse(True)
    except Exception:
      pass

  def test47RWMols(self):
    """ test the RWMol class

    """
    mol = Chem.MolFromSmiles('C1CCC1')
    self.assertTrue(type(mol) == Chem.Mol)

    for rwmol in [Chem.EditableMol(mol), Chem.RWMol(mol)]:
      self.assertTrue(type(rwmol) in [Chem.EditableMol, Chem.RWMol])
      newAt = Chem.Atom(8)
      rwmol.ReplaceAtom(0, newAt)
      self.assertTrue(Chem.MolToSmiles(rwmol.GetMol()) == 'C1COC1')

      rwmol.RemoveBond(0, 1)
      self.assertTrue(Chem.MolToSmiles(rwmol.GetMol()) == 'CCCO')
      a = Chem.Atom(7)
      idx = rwmol.AddAtom(a)
      self.assertEqual(rwmol.GetMol().GetNumAtoms(), 5)
      self.assertEqual(idx, 4)

      idx = rwmol.AddBond(0, 4, order=Chem.BondType.SINGLE)
      self.assertEqual(idx, 4)

      self.assertTrue(Chem.MolToSmiles(rwmol.GetMol()) == 'CCCON')
      rwmol.AddBond(4, 1, order=Chem.BondType.SINGLE)
      self.assertTrue(Chem.MolToSmiles(rwmol.GetMol()) == 'C1CNOC1')

      rwmol.RemoveAtom(3)
      self.assertTrue(Chem.MolToSmiles(rwmol.GetMol()) == 'CCNO')

      # practice shooting ourselves in the foot:
      m = Chem.MolFromSmiles('c1ccccc1')
      em = Chem.EditableMol(m)
      em.RemoveAtom(0)
      m2 = em.GetMol()
      self.assertRaises(ValueError, lambda: Chem.SanitizeMol(m2))
      m = Chem.MolFromSmiles('c1ccccc1')
      em = Chem.EditableMol(m)
      em.RemoveBond(0, 1)
      m2 = em.GetMol()
      self.assertRaises(ValueError, lambda: Chem.SanitizeMol(m2))

      # boundary cases:

      # removing non-existent bonds:
      m = Chem.MolFromSmiles('c1ccccc1')
      em = Chem.EditableMol(m)
      em.RemoveBond(0, 2)
      m2 = em.GetMol()
      Chem.SanitizeMol(m2)
      self.assertTrue(Chem.MolToSmiles(m2) == 'c1ccccc1')

      # removing non-existent atoms:
      m = Chem.MolFromSmiles('c1ccccc1')
      em = Chem.EditableMol(m)
      self.assertRaises(RuntimeError, lambda: em.RemoveAtom(12))

      # confirm that an RWMol can be constructed without arguments
      m = Chem.RWMol()

    # test replaceAtom/Bond preserving properties
    mol = Chem.MolFromSmiles('C1CCC1')
    mol2 = Chem.MolFromSmiles('C1CCC1')
    mol.GetAtomWithIdx(0).SetProp("foo", "bar")
    mol.GetBondWithIdx(0).SetProp("foo", "bar")
    newBond = mol2.GetBondWithIdx(0)
    self.assertTrue(type(mol) == Chem.Mol)

    for rwmol in [Chem.EditableMol(mol), Chem.RWMol(mol)]:
      newAt = Chem.Atom(8)
      rwmol.ReplaceAtom(0, newAt)
      self.assertTrue(Chem.MolToSmiles(rwmol.GetMol()) == 'C1COC1')
      self.assertFalse(rwmol.GetMol().GetAtomWithIdx(0).HasProp("foo"))

    for rwmol in [Chem.EditableMol(mol), Chem.RWMol(mol)]:
      newAt = Chem.Atom(8)
      rwmol.ReplaceAtom(0, newAt, preserveProps=True)
      self.assertTrue(Chem.MolToSmiles(rwmol.GetMol()) == 'C1COC1')
      self.assertTrue(rwmol.GetMol().GetAtomWithIdx(0).HasProp("foo"))
      self.assertEqual(rwmol.GetMol().GetAtomWithIdx(0).GetProp("foo"), "bar")

    for rwmol in [Chem.EditableMol(mol), Chem.RWMol(mol)]:
      rwmol.ReplaceBond(0, newBond)
      self.assertTrue(Chem.MolToSmiles(rwmol.GetMol()) == 'C1CCC1')
      self.assertFalse(rwmol.GetMol().GetBondWithIdx(0).HasProp("foo"))

    for rwmol in [Chem.EditableMol(mol), Chem.RWMol(mol)]:
      rwmol.ReplaceBond(0, newBond, preserveProps=True)
      self.assertTrue(Chem.MolToSmiles(rwmol.GetMol()) == 'C1CCC1')
      self.assertTrue(rwmol.GetMol().GetBondWithIdx(0).HasProp("foo"))
      self.assertEqual(rwmol.GetMol().GetBondWithIdx(0).GetProp("foo"), "bar")

  def test47SmartsPieces(self):
    """ test the GetAtomSmarts and GetBondSmarts functions

    """
    m = Chem.MolFromSmarts("[C,N]C")
    self.assertTrue(m.GetAtomWithIdx(0).GetSmarts() == '[C,N]')
    self.assertTrue(m.GetAtomWithIdx(1).GetSmarts() == 'C')
    self.assertEqual(m.GetBondBetweenAtoms(0, 1).GetSmarts(), '')

    m = Chem.MolFromSmarts("[$(C=O)]-O")
    self.assertTrue(m.GetAtomWithIdx(0).GetSmarts() == '[$(C=O)]')
    self.assertTrue(m.GetAtomWithIdx(1).GetSmarts() == 'O')
    self.assertTrue(m.GetBondBetweenAtoms(0, 1).GetSmarts() == '-')

    m = Chem.MolFromSmiles("CO")
    self.assertTrue(m.GetAtomWithIdx(0).GetSmarts() == 'C')
    self.assertTrue(m.GetAtomWithIdx(1).GetSmarts() == 'O')
    self.assertTrue(m.GetBondBetweenAtoms(0, 1).GetSmarts() == '')
    self.assertTrue(m.GetBondBetweenAtoms(0, 1).GetSmarts(allBondsExplicit=True) == '-')

    m = Chem.MolFromSmiles("C=O")
    self.assertTrue(m.GetAtomWithIdx(0).GetSmarts() == 'C')
    self.assertTrue(m.GetAtomWithIdx(1).GetSmarts() == 'O')
    self.assertTrue(m.GetBondBetweenAtoms(0, 1).GetSmarts() == '=')

    m = Chem.MolFromSmiles('C[C@H](F)[15NH3+]')
    self.assertEqual(m.GetAtomWithIdx(0).GetSmarts(), 'C')
    self.assertEqual(m.GetAtomWithIdx(0).GetSmarts(allHsExplicit=True), '[CH3]')
    self.assertEqual(m.GetAtomWithIdx(3).GetSmarts(), '[15NH3+]')
    self.assertEqual(m.GetAtomWithIdx(3).GetSmarts(allHsExplicit=True), '[15NH3+]')

  def test48Issue1928819(self):
    """ test a crash involving looping directly over mol suppliers
    """
    fileN = os.path.join(RDConfig.RDBaseDir, 'Code', 'GraphMol', 'FileParsers', 'test_data',
                         'NCI_aids_few.sdf')
    ms = [x for x in Chem.SDMolSupplier(fileN)]
    self.assertEqual(len(ms), 16)
    count = 0
    for m in Chem.SDMolSupplier(fileN):
      count += 1
    self.assertEqual(count, 16)

    fileN = os.path.join(RDConfig.RDBaseDir, 'Code', 'GraphMol', 'FileParsers', 'test_data',
                         'fewSmi.csv')
    count = 0
    for m in Chem.SmilesMolSupplier(fileN, titleLine=False, smilesColumn=1, delimiter=','):
      count += 1
    self.assertEqual(count, 10)

    fileN = os.path.join(RDConfig.RDBaseDir, 'Code', 'GraphMol', 'FileParsers', 'test_data',
                         'acd_few.tdt')
    count = 0
    for m in Chem.TDTMolSupplier(fileN):
      count += 1
    self.assertEqual(count, 10)

  def test49Issue1932365(self):
    """ test aromatic Se and Te from smiles/smarts
    """
    m = Chem.MolFromSmiles('c1ccc[se]1')
    self.assertTrue(m)
    self.assertTrue(m.GetAtomWithIdx(0).GetIsAromatic())
    self.assertTrue(m.GetAtomWithIdx(4).GetIsAromatic())
    m = Chem.MolFromSmiles('c1ccc[te]1')
    self.assertTrue(m)
    self.assertTrue(m.GetAtomWithIdx(0).GetIsAromatic())
    self.assertTrue(m.GetAtomWithIdx(4).GetIsAromatic())
    m = Chem.MolFromSmiles('C1=C[Se]C=C1')
    self.assertTrue(m)
    self.assertTrue(m.GetAtomWithIdx(0).GetIsAromatic())
    self.assertTrue(m.GetAtomWithIdx(2).GetIsAromatic())
    m = Chem.MolFromSmiles('C1=C[Te]C=C1')
    self.assertTrue(m)
    self.assertTrue(m.GetAtomWithIdx(0).GetIsAromatic())
    self.assertTrue(m.GetAtomWithIdx(2).GetIsAromatic())

    p = Chem.MolFromSmarts('[se]')
    self.assertTrue(Chem.MolFromSmiles('c1ccc[se]1').HasSubstructMatch(p))
    self.assertFalse(Chem.MolFromSmiles('C1=CCC[Se]1').HasSubstructMatch(p))

    p = Chem.MolFromSmarts('[te]')
    self.assertTrue(Chem.MolFromSmiles('c1ccc[te]1').HasSubstructMatch(p))
    self.assertFalse(Chem.MolFromSmiles('C1=CCC[Te]1').HasSubstructMatch(p))

  def test50Issue1968608(self):
    """ test sf.net issue 1968608
    """
    smarts = Chem.MolFromSmarts("[r5]")
    mol = Chem.MolFromSmiles("N12CCC36C1CC(C(C2)=CCOC4CC5=O)C4C3N5c7ccccc76")
    count = len(mol.GetSubstructMatches(smarts, uniquify=0))
    self.assertTrue(count == 9)

  def test51RadicalHandling(self):
    """ test handling of atoms with radicals
    """
    mol = Chem.MolFromSmiles("[C]C")
    self.assertTrue(mol)
    atom = mol.GetAtomWithIdx(0)
    self.assertTrue(atom.GetNumRadicalElectrons() == 3)
    self.assertTrue(atom.GetNoImplicit())
    atom.SetNoImplicit(False)
    atom.SetNumRadicalElectrons(1)
    mol.UpdatePropertyCache()
    self.assertTrue(atom.GetNumRadicalElectrons() == 1)
    self.assertTrue(atom.GetNumImplicitHs() == 2)

    mol = Chem.MolFromSmiles("[c]1ccccc1")
    self.assertTrue(mol)
    atom = mol.GetAtomWithIdx(0)
    self.assertTrue(atom.GetNumRadicalElectrons() == 1)
    self.assertTrue(atom.GetNoImplicit())

    mol = Chem.MolFromSmiles("[n]1ccccc1")
    self.assertTrue(mol)
    atom = mol.GetAtomWithIdx(0)
    self.assertTrue(atom.GetNumRadicalElectrons() == 0)
    self.assertTrue(atom.GetNoImplicit())

  def test52MolFrags(self):
    """ test GetMolFrags functionality
    """
    mol = Chem.MolFromSmiles("C.CC")
    self.assertTrue(mol)
    fs = Chem.GetMolFrags(mol)
    self.assertTrue(len(fs) == 2)
    self.assertTrue(len(fs[0]) == 1)
    self.assertTrue(tuple(fs[0]) == (0, ))
    self.assertTrue(len(fs[1]) == 2)
    self.assertTrue(tuple(fs[1]) == (1, 2))

    fs = Chem.GetMolFrags(mol, True)
    self.assertTrue(len(fs) == 2)
    self.assertTrue(fs[0].GetNumAtoms() == 1)
    self.assertTrue(fs[1].GetNumAtoms() == 2)

    mol = Chem.MolFromSmiles("CCC")
    self.assertTrue(mol)
    fs = Chem.GetMolFrags(mol)
    self.assertTrue(len(fs) == 1)
    self.assertTrue(len(fs[0]) == 3)
    self.assertTrue(tuple(fs[0]) == (0, 1, 2))
    fs = Chem.GetMolFrags(mol, True)
    self.assertTrue(len(fs) == 1)
    self.assertTrue(fs[0].GetNumAtoms() == 3)

    mol = Chem.MolFromSmiles("CO")
    em = Chem.EditableMol(mol)
    em.RemoveBond(0, 1)
    nm = em.GetMol()
    fs = Chem.GetMolFrags(nm, asMols=True)
    self.assertEqual([x.GetNumAtoms(onlyExplicit=False) for x in fs], [5, 3])
    fs = Chem.GetMolFrags(nm, asMols=True, sanitizeFrags=False)
    self.assertEqual([x.GetNumAtoms(onlyExplicit=False) for x in fs], [4, 2])

    mol = Chem.MolFromSmiles("CC.CCC")
    fs = Chem.GetMolFrags(mol, asMols=True)
    self.assertEqual([x.GetNumAtoms() for x in fs], [2, 3])
    frags = []
    fragsMolAtomMapping = []
    fs = Chem.GetMolFrags(mol, asMols=True, frags=frags, fragsMolAtomMapping=fragsMolAtomMapping)
    self.assertEqual(mol.GetNumAtoms(onlyExplicit=True), len(frags))
    fragsCheck = []
    for i, f in enumerate(fs):
      fragsCheck.extend([i] * f.GetNumAtoms(onlyExplicit=True))
    self.assertEqual(frags, fragsCheck)
    fragsMolAtomMappingCheck = []
    i = 0
    for f in fs:
      n = f.GetNumAtoms(onlyExplicit=True)
      fragsMolAtomMappingCheck.append(tuple(range(i, i + n)))
      i += n
    self.assertEqual(fragsMolAtomMapping, fragsMolAtomMappingCheck)

  def test53Matrices(self):
    """ test adjacency and distance matrices

    """
    m = Chem.MolFromSmiles('CC=C')
    d = Chem.GetDistanceMatrix(m, 0)
    self.assertTrue(feq(d[0, 1], 1.0))
    self.assertTrue(feq(d[0, 2], 2.0))
    self.assertTrue(feq(d[1, 0], 1.0))
    self.assertTrue(feq(d[2, 0], 2.0))
    a = Chem.GetAdjacencyMatrix(m, 0)
    self.assertTrue(a[0, 1] == 1)
    self.assertTrue(a[0, 2] == 0)
    self.assertTrue(a[1, 2] == 1)
    self.assertTrue(a[1, 0] == 1)
    self.assertTrue(a[2, 0] == 0)

    m = Chem.MolFromSmiles('C1CC1')
    d = Chem.GetDistanceMatrix(m, 0)
    self.assertTrue(feq(d[0, 1], 1.0))
    self.assertTrue(feq(d[0, 2], 1.0))
    a = Chem.GetAdjacencyMatrix(m, 0)
    self.assertTrue(a[0, 1] == 1)
    self.assertTrue(a[0, 2] == 1)
    self.assertTrue(a[1, 2] == 1)

    m = Chem.MolFromSmiles('CC.C')
    d = Chem.GetDistanceMatrix(m, 0)
    self.assertTrue(feq(d[0, 1], 1.0))
    self.assertTrue(d[0, 2] > 1000)
    self.assertTrue(d[1, 2] > 1000)
    a = Chem.GetAdjacencyMatrix(m, 0)
    self.assertTrue(a[0, 1] == 1)
    self.assertTrue(a[0, 2] == 0)
    self.assertTrue(a[1, 2] == 0)

  def test54Mol2Parser(self):
    """ test the mol2 parser
    """
    fileN = os.path.join(RDConfig.RDBaseDir, 'Code', 'GraphMol', 'FileParsers', 'test_data',
                         'pyrazole_pyridine.mol2')
    m = Chem.MolFromMol2File(fileN)
    self.assertTrue(m.GetNumAtoms() == 5)
    self.assertTrue(Chem.MolToSmiles(m) == 'c1cn[nH]c1', Chem.MolToSmiles(m))

    fileN = os.path.join(RDConfig.RDBaseDir, 'Code', 'GraphMol', 'FileParsers', 'test_data',
                         '3505.mol2')
    m = Chem.MolFromMol2File(fileN)
    self.assertTrue(m.GetBondBetweenAtoms(3, 12) is not None)
    self.assertEqual(m.GetBondBetweenAtoms(3, 12).GetBondType(), Chem.BondType.SINGLE)
    self.assertEqual(m.GetAtomWithIdx(12).GetFormalCharge(), 0)

    m = Chem.MolFromMol2File(fileN, cleanupSubstructures=False)
    self.assertTrue(m.GetBondBetweenAtoms(3, 12) is not None)
    self.assertEqual(m.GetBondBetweenAtoms(3, 12).GetBondType(), Chem.BondType.DOUBLE)
    self.assertEqual(m.GetAtomWithIdx(12).GetFormalCharge(), 1)

  def test55LayeredFingerprint(self):
    m1 = Chem.MolFromSmiles('CC(C)C')
    fp1 = Chem.LayeredFingerprint(m1)
    self.assertEqual(len(fp1), 2048)
    atomCounts = [0] * m1.GetNumAtoms()
    fp2 = Chem.LayeredFingerprint(m1, atomCounts=atomCounts)
    self.assertEqual(fp1, fp2)
    self.assertEqual(atomCounts, [4, 7, 4, 4])

    fp2 = Chem.LayeredFingerprint(m1, atomCounts=atomCounts)
    self.assertEqual(fp1, fp2)
    self.assertEqual(atomCounts, [8, 14, 8, 8])

    pbv = DataStructs.ExplicitBitVect(2048)
    fp3 = Chem.LayeredFingerprint(m1, setOnlyBits=pbv)
    self.assertEqual(fp3.GetNumOnBits(), 0)

    fp3 = Chem.LayeredFingerprint(m1, setOnlyBits=fp2)
    self.assertEqual(fp3, fp2)

    m2 = Chem.MolFromSmiles('CC')
    fp4 = Chem.LayeredFingerprint(m2)
    atomCounts = [0] * m1.GetNumAtoms()
    fp3 = Chem.LayeredFingerprint(m1, setOnlyBits=fp4, atomCounts=atomCounts)
    self.assertEqual(atomCounts, [1, 3, 1, 1])

    m2 = Chem.MolFromSmiles('CCC')
    fp4 = Chem.LayeredFingerprint(m2)
    atomCounts = [0] * m1.GetNumAtoms()
    fp3 = Chem.LayeredFingerprint(m1, setOnlyBits=fp4, atomCounts=atomCounts)
    self.assertEqual(atomCounts, [3, 6, 3, 3])

  def test56LazySDMolSupplier(self):
    if not hasattr(Chem, 'CompressedSDMolSupplier'):
      return

    self.assertRaises(ValueError, lambda: Chem.CompressedSDMolSupplier('nosuchfile.sdf.gz'))

    fileN = os.path.join(RDConfig.RDBaseDir, 'Code', 'GraphMol', 'FileParsers', 'test_data',
                         'NCI_aids_few.sdf.gz')
    sdSup = Chem.CompressedSDMolSupplier(fileN)
    molNames = [
      "48", "78", "128", "163", "164", "170", "180", "186", "192", "203", "210", "211", "213",
      "220", "229", "256"
    ]

    chgs192 = {8: 1, 11: 1, 15: -1, 18: -1, 20: 1, 21: 1, 23: -1, 25: -1}
    i = 0
    for mol in sdSup:
      self.assertTrue(mol)
      self.assertTrue(mol.GetProp("_Name") == molNames[i])
      i += 1
      if (mol.GetProp("_Name") == "192"):
        # test parsed charges on one of the molecules
        for id in chgs192.keys():
          self.assertTrue(mol.GetAtomWithIdx(id).GetFormalCharge() == chgs192[id])
    self.assertEqual(i, 16)

    sdSup = Chem.CompressedSDMolSupplier(fileN)
    ns = [mol.GetProp("_Name") for mol in sdSup]
    self.assertTrue(ns == molNames)

    sdSup = Chem.CompressedSDMolSupplier(fileN, 0)
    for mol in sdSup:
      self.assertTrue(not mol.HasProp("numArom"))

  def test57AddRecursiveQuery(self):
    q1 = Chem.MolFromSmiles('CC')
    q2 = Chem.MolFromSmiles('CO')
    Chem.AddRecursiveQuery(q1, q2, 1)

    m1 = Chem.MolFromSmiles('OCC')
    self.assertTrue(m1.HasSubstructMatch(q2))
    self.assertTrue(m1.HasSubstructMatch(q1))
    self.assertTrue(m1.HasSubstructMatch(q1))
    self.assertTrue(m1.GetSubstructMatch(q1) == (2, 1))

    q3 = Chem.MolFromSmiles('CS')
    Chem.AddRecursiveQuery(q1, q3, 1)

    self.assertFalse(m1.HasSubstructMatch(q3))
    self.assertFalse(m1.HasSubstructMatch(q1))

    m2 = Chem.MolFromSmiles('OC(S)C')
    self.assertTrue(m2.HasSubstructMatch(q1))
    self.assertTrue(m2.GetSubstructMatch(q1) == (3, 1))

    m3 = Chem.MolFromSmiles('SCC')
    self.assertTrue(m3.HasSubstructMatch(q3))
    self.assertFalse(m3.HasSubstructMatch(q1))

    q1 = Chem.MolFromSmiles('CC')
    Chem.AddRecursiveQuery(q1, q2, 1)
    Chem.AddRecursiveQuery(q1, q3, 1, False)
    self.assertTrue(m3.HasSubstructMatch(q1))
    self.assertTrue(m3.GetSubstructMatch(q1) == (2, 1))

  def test58Issue2983794(self):
    fileN = os.path.join(RDConfig.RDBaseDir, 'Code', 'GraphMol', 'Wrap', 'test_data',
                         'issue2983794.sdf')
    m1 = Chem.MolFromMolFile(fileN)
    self.assertTrue(m1)
    em = Chem.EditableMol(m1)
    em.RemoveAtom(0)
    m2 = em.GetMol()
    Chem.Kekulize(m2)

  def test59Issue3007178(self):
    m = Chem.MolFromSmiles('CCC')
    a = m.GetAtomWithIdx(0)
    m = None
    self.assertEqual(Chem.MolToSmiles(a.GetOwningMol()), 'CCC')
    a = None
    m = Chem.MolFromSmiles('CCC')
    b = m.GetBondWithIdx(0)
    m = None
    self.assertEqual(Chem.MolToSmiles(b.GetOwningMol()), 'CCC')

  def test60SmilesWriterClose(self):
    fileN = os.path.join(RDConfig.RDBaseDir, 'Code', 'GraphMol', 'FileParsers', 'test_data',
                         'fewSmi.csv')
    smiSup = Chem.SmilesMolSupplier(fileN, delimiter=",", smilesColumn=1, nameColumn=0, titleLine=0)
    ms = [x for x in smiSup]

    ofile = os.path.join(RDConfig.RDBaseDir, 'Code', 'GraphMol', 'Wrap', 'test_data',
                         'outSmiles.txt')
    writer = Chem.SmilesWriter(ofile)
    for mol in ms:
      writer.write(mol)
    writer.close()

    newsup = Chem.SmilesMolSupplier(ofile)
    newms = [x for x in newsup]
    self.assertEqual(len(ms), len(newms))

  def test61PathToSubmol(self):
    m = Chem.MolFromSmiles('CCCCCC1C(O)CC(O)N1C=CCO')
    env = Chem.FindAtomEnvironmentOfRadiusN(m, 2, 11)
    self.assertEqual(len(env), 8)
    amap = {}
    submol = Chem.PathToSubmol(m, env, atomMap=amap)
    self.assertEqual(submol.GetNumAtoms(), len(amap.keys()))
    self.assertEqual(submol.GetNumAtoms(), 9)
    smi = Chem.MolToSmiles(submol, rootedAtAtom=amap[11])
    self.assertEqual(smi[0], 'N')
    refsmi = Chem.MolToSmiles(Chem.MolFromSmiles('N(C=C)(C(C)C)C(O)C'))
    csmi = Chem.MolToSmiles(Chem.MolFromSmiles(smi))
    self.assertEqual(refsmi, csmi)

  def test62SmilesAndSmartsReplacements(self):
    mol = Chem.MolFromSmiles('C{branch}C', replacements={'{branch}': 'C1(CC1)'})
    self.assertEqual(mol.GetNumAtoms(), 5)
    mol = Chem.MolFromSmarts('C{branch}C', replacements={'{branch}': 'C1(CC1)'})
    self.assertEqual(mol.GetNumAtoms(), 5)
    mol = Chem.MolFromSmiles('C{branch}C{acid}', replacements={
      '{branch}': 'C1(CC1)',
      '{acid}': "C(=O)O"
    })
    self.assertEqual(mol.GetNumAtoms(), 8)

  def test63Issue3313539(self):
    fileN = os.path.join(RDConfig.RDBaseDir, 'Code', 'GraphMol', 'FileParsers', 'test_data',
                         'rgroups1.mol')
    m = Chem.MolFromMolFile(fileN)
    self.assertTrue(m is not None)
    at = m.GetAtomWithIdx(3)
    self.assertTrue(at is not None)
    self.assertTrue(at.HasProp('_MolFileRLabel'))
    p = at.GetProp('_MolFileRLabel')
    self.assertEqual(p, '2')
    self.assertEqual(Chem.GetAtomRLabel(at), 2)

    at = m.GetAtomWithIdx(4)
    self.assertTrue(at is not None)
    self.assertTrue(at.HasProp('_MolFileRLabel'))
    p = at.GetProp('_MolFileRLabel')
    self.assertEqual(p, '1')
    self.assertEqual(Chem.GetAtomRLabel(at), 1)

  def test64MoleculeCleanup(self):
    m = Chem.MolFromSmiles('CN(=O)=O', False)
    self.assertTrue(m)
    self.assertTrue(
      m.GetAtomWithIdx(1).GetFormalCharge() == 0 and m.GetAtomWithIdx(2).GetFormalCharge() == 0
      and m.GetAtomWithIdx(3).GetFormalCharge() == 0)
    self.assertTrue(
      m.GetBondBetweenAtoms(1, 3).GetBondType() == Chem.BondType.DOUBLE
      and m.GetBondBetweenAtoms(1, 2).GetBondType() == Chem.BondType.DOUBLE)
    Chem.Cleanup(m)
    m.UpdatePropertyCache()
    self.assertTrue(
      m.GetAtomWithIdx(1).GetFormalCharge() == 1 and
      (m.GetAtomWithIdx(2).GetFormalCharge() == -1 or m.GetAtomWithIdx(3).GetFormalCharge() == -1))
    self.assertTrue(
      m.GetBondBetweenAtoms(1, 3).GetBondType() == Chem.BondType.SINGLE
      or m.GetBondBetweenAtoms(1, 2).GetBondType() == Chem.BondType.SINGLE)

  def test65StreamSupplier(self):
    fileN = os.path.join(RDConfig.RDBaseDir, 'Code', 'GraphMol', 'FileParsers', 'test_data',
                         'NCI_aids_few.sdf.gz')
    molNames = [
      "48", "78", "128", "163", "164", "170", "180", "186", "192", "203", "210", "211", "213",
      "220", "229", "256"
    ]
    inf = gzip.open(fileN)
    if 0:
      sb = Chem.streambuf(inf)
      suppl = Chem.ForwardSDMolSupplier(sb)
    else:
      suppl = Chem.ForwardSDMolSupplier(inf)

    i = 0
    while not suppl.atEnd():
      mol = next(suppl)
      self.assertTrue(mol)
      self.assertTrue(mol.GetProp("_Name") == molNames[i])
      i += 1
    self.assertEqual(i, 16)

    # make sure we have object ownership preserved
    inf = gzip.open(fileN)
    suppl = Chem.ForwardSDMolSupplier(inf)
    inf = None
    i = 0
    while not suppl.atEnd():
      mol = next(suppl)
      self.assertTrue(mol)
      self.assertTrue(mol.GetProp("_Name") == molNames[i])
      i += 1
    self.assertEqual(i, 16)

  @unittest.skipIf(not hasattr(Chem, 'MaeMolSupplier'), "not built with MAEParser support")
  def testMaeStreamSupplier(self):
    fileN = os.path.join(RDConfig.RDBaseDir, 'Code', 'GraphMol', 'FileParsers', 'test_data',
                         'NCI_aids_few.maegz')
    molNames = [
      "48", "78", "128", "163", "164", "170", "180", "186", "192", "203", "210", "211", "213",
      "220", "229", "256"
    ]
    inf = gzip.open(fileN)
    suppl = Chem.MaeMolSupplier(inf)

    i = 0
    while not suppl.atEnd():
      mol = next(suppl)
      self.assertTrue(mol)
      self.assertTrue(mol.GetProp("_Name") == molNames[i])
      i += 1
    self.assertEqual(i, 16)

    # make sure we have object ownership preserved
    inf = gzip.open(fileN)
    suppl = Chem.MaeMolSupplier(inf)
    inf = None
    i = 0
    while not suppl.atEnd():
      mol = next(suppl)
      self.assertTrue(mol)
      self.assertTrue(mol.GetProp("_Name") == molNames[i])
      i += 1
    self.assertEqual(i, 16)

  @unittest.skipIf(not hasattr(Chem, 'MaeMolSupplier'), "not built with MAEParser support")
  def testMaeFileSupplier(self):
    fileN = os.path.join(RDConfig.RDBaseDir, 'Code', 'GraphMol', 'FileParsers', 'test_data',
                         'NCI_aids_few.mae')
    molNames = [
      "48", "78", "128", "163", "164", "170", "180", "186", "192", "203", "210", "211", "213",
      "220", "229", "256"
    ]
    suppl = Chem.MaeMolSupplier(fileN)

    i = 0
    while not suppl.atEnd():
      mol = next(suppl)
      self.assertTrue(mol)
      self.assertTrue(mol.GetProp("_Name") == molNames[i])
      i += 1
    self.assertEqual(i, 16)

  @unittest.skipIf(not hasattr(Chem, 'MaeMolSupplier'), "not built with MAEParser support")
  def testMaeFileSupplierException(self):
    fileN = os.path.join(RDConfig.RDBaseDir, 'Code', 'GraphMol', 'FileParsers', 'test_data',
                         'bad_ppty.mae')
    err_msg_substr = "Bad format for property"

    ok = False
    suppl = Chem.MaeMolSupplier(fileN)
    for i in range(5):
      try:
        mol = next(suppl)
      except RuntimeError as e:
        self.assertEqual(i, 1)
        self.assertTrue(err_msg_substr in str(e))
        ok = True
        break
      else:
        self.assertTrue(mol)
        self.assertTrue(mol.HasProp("_Name"))
        self.assertTrue(mol.GetNumAtoms() == 1)

    self.assertFalse(suppl.atEnd())
    self.assertTrue(ok)

  @unittest.skipIf(not hasattr(Chem, 'MaeMolSupplier'), "not built with MAEParser support")
  def testMaeFileSupplierSetData(self):
    fileN = os.path.join(RDConfig.RDBaseDir, 'Code', 'GraphMol', 'FileParsers', 'test_data',
                         'NCI_aids_few.mae')
    molNames = [
      "48", "78", "128", "163", "164", "170", "180", "186", "192", "203", "210", "211", "213",
      "220", "229", "256"
    ]
    suppl = Chem.MaeMolSupplier()

    with open(fileN) as f:
      data = f.read()

    suppl.SetData(data)

    self.assertEqual(len(suppl), 16)

    for i, mol in enumerate(suppl):
      self.assertTrue(mol)
      self.assertEqual(mol.GetProp("_Name"), molNames[i])

    self.assertEqual(i, 15)

    # Do it again, to check the reset() method
    suppl.reset()

    self.assertEqual(len(suppl), 16)

    for i, mol in enumerate(suppl):
      self.assertTrue(mol)
      self.assertEqual(mol.GetProp("_Name"), molNames[i])

    self.assertEqual(i, 15)

  @unittest.skipIf(not hasattr(Chem, 'MaeMolSupplier'), "not built with MAEParser support")
  def testMaeFileSupplierGetItem(self):
    fileN = os.path.join(RDConfig.RDBaseDir, 'Code', 'GraphMol', 'FileParsers', 'test_data',
                         'NCI_aids_few.mae')
    molNames = [
      "48", "78", "128", "163", "164", "170", "180", "186", "192", "203", "210", "211", "213",
      "220", "229", "256"
    ]
    with Chem.MaeMolSupplier(fileN) as suppl:
      num_mols = len(suppl)
      self.assertEqual(num_mols, 16)

      for i in range(num_mols):
        self.assertEqual(suppl[i].GetProp("_Name"), molNames[i])

        j = -(i + 1)
        self.assertEqual(suppl[j].GetProp("_Name"), molNames[j])

      for i in (num_mols, 2 * num_mols, -(num_mols + 1), -2 * (num_mols + 1)):
        self.assertRaises(IndexError, lambda: suppl[i])

  @unittest.skipIf(not hasattr(Chem, 'MaeMolSupplier'), "not built with MAEParser support")
  def testMaeFileSupplierExceptionMsgs(self):
    maeBlock = "f_m_ct {\n  s_m_title\n  :::\n  " "\n  }\n}"

    with Chem.MaeMolSupplier() as suppl:
      suppl.SetData(maeBlock)
      self.assertRaisesRegex(RuntimeError, r'File parsing error: Indexed block not found: m_atom',
                             lambda: next(suppl))

  def test66StreamSupplierIter(self):
    fileN = os.path.join(RDConfig.RDBaseDir, 'Code', 'GraphMol', 'FileParsers', 'test_data',
                         'NCI_aids_few.sdf.gz')
    inf = gzip.open(fileN)
    if 0:
      sb = Chem.streambuf(inf)
      suppl = Chem.ForwardSDMolSupplier(sb)
    else:
      suppl = Chem.ForwardSDMolSupplier(inf)

    molNames = [
      "48", "78", "128", "163", "164", "170", "180", "186", "192", "203", "210", "211", "213",
      "220", "229", "256"
    ]
    i = 0
    for mol in suppl:
      self.assertTrue(mol)
      self.assertTrue(mol.GetProp("_Name") == molNames[i])
      i += 1
    self.assertEqual(i, 16)

  def test67StreamSupplierStringIO(self):
    fileN = os.path.join(RDConfig.RDBaseDir, 'Code', 'GraphMol', 'FileParsers', 'test_data',
                         'NCI_aids_few.sdf.gz')
    sio = BytesIO(gzip.open(fileN).read())
    suppl = Chem.ForwardSDMolSupplier(sio)
    molNames = [
      "48", "78", "128", "163", "164", "170", "180", "186", "192", "203", "210", "211", "213",
      "220", "229", "256"
    ]
    i = 0
    for mol in suppl:
      self.assertTrue(mol)
      self.assertTrue(mol.GetProp("_Name") == molNames[i])
      i += 1
    self.assertEqual(i, 16)

  def test68ForwardSupplierUsingFilename(self):
    fileN = os.path.join(RDConfig.RDBaseDir, 'Code', 'GraphMol', 'FileParsers', 'test_data',
                         'NCI_aids_few.sdf')
    suppl = Chem.ForwardSDMolSupplier(fileN)
    molNames = [
      "48", "78", "128", "163", "164", "170", "180", "186", "192", "203", "210", "211", "213",
      "220", "229", "256"
    ]
    i = 0
    for mol in suppl:
      self.assertTrue(mol)
      self.assertTrue(mol.GetProp("_Name") == molNames[i])
      i += 1
    self.assertEqual(i, 16)

    self.assertRaises(IOError, lambda: Chem.ForwardSDMolSupplier('nosuchfile.sdf'))

  def test69StreamSupplierStreambuf(self):
    fileN = os.path.join(RDConfig.RDBaseDir, 'Code', 'GraphMol', 'FileParsers', 'test_data',
                         'NCI_aids_few.sdf.gz')
    sb = rdBase.streambuf(gzip.open(fileN))
    suppl = Chem.ForwardSDMolSupplier(sb)

    molNames = [
      "48", "78", "128", "163", "164", "170", "180", "186", "192", "203", "210", "211", "213",
      "220", "229", "256"
    ]
    i = 0
    for mol in suppl:
      self.assertTrue(mol)
      self.assertTrue(mol.GetProp("_Name") == molNames[i])
      i += 1
    self.assertEqual(i, 16)

  def test70StreamSDWriter(self):
    fileN = os.path.join(RDConfig.RDBaseDir, 'Code', 'GraphMol', 'FileParsers', 'test_data',
                         'NCI_aids_few.sdf.gz')
    inf = gzip.open(fileN)
    suppl = Chem.ForwardSDMolSupplier(inf)
    osio = StringIO()
    w = Chem.SDWriter(osio)
    molNames = [
      "48", "78", "128", "163", "164", "170", "180", "186", "192", "203", "210", "211", "213",
      "220", "229", "256"
    ]
    i = 0
    for mol in suppl:
      self.assertTrue(mol)
      self.assertTrue(mol.GetProp("_Name") == molNames[i])
      w.write(mol)
      i += 1
    self.assertEqual(i, 16)
    w.flush()
    w = None
    txt = osio.getvalue().encode()
    isio = BytesIO(txt)
    suppl = Chem.ForwardSDMolSupplier(isio)
    i = 0
    for mol in suppl:
      self.assertTrue(mol)
      self.assertTrue(mol.GetProp("_Name") == molNames[i])
      i += 1
    self.assertEqual(i, 16)

  def test71StreamSmilesWriter(self):
    fileN = os.path.join(RDConfig.RDBaseDir, 'Code', 'GraphMol', 'FileParsers', 'test_data',
                         'esters.sdf')
    suppl = Chem.ForwardSDMolSupplier(fileN)
    osio = StringIO()
    w = Chem.SmilesWriter(osio)
    ms = [x for x in suppl]
    w.SetProps(ms[0].GetPropNames())
    i = 0
    for mol in ms:
      self.assertTrue(mol)
      w.write(mol)
      i += 1
    self.assertEqual(i, 6)
    w.flush()
    w = None
    txt = osio.getvalue()
    self.assertEqual(txt.count('ID'), 1)
    self.assertEqual(txt.count('\n'), 7)

  def test72StreamTDTWriter(self):
    fileN = os.path.join(RDConfig.RDBaseDir, 'Code', 'GraphMol', 'FileParsers', 'test_data',
                         'esters.sdf')
    suppl = Chem.ForwardSDMolSupplier(fileN)
    osio = StringIO()
    w = Chem.TDTWriter(osio)
    ms = [x for x in suppl]
    w.SetProps(ms[0].GetPropNames())
    i = 0
    for mol in ms:
      self.assertTrue(mol)
      w.write(mol)
      i += 1
    self.assertEqual(i, 6)
    w.flush()
    w = None
    txt = osio.getvalue()
    self.assertEqual(txt.count('ID'), 6)
    self.assertEqual(txt.count('NAME'), 6)

  def test73SanitizationOptions(self):
    m = Chem.MolFromSmiles('c1ccccc1', sanitize=False)
    res = Chem.SanitizeMol(m, catchErrors=True)
    self.assertEqual(res, 0)

    m = Chem.MolFromSmiles('c1cccc1', sanitize=False)
    res = Chem.SanitizeMol(m, catchErrors=True)
    self.assertEqual(res, Chem.SanitizeFlags.SANITIZE_KEKULIZE)

    m = Chem.MolFromSmiles('CC(C)(C)(C)C', sanitize=False)
    res = Chem.SanitizeMol(m, catchErrors=True)
    self.assertEqual(res, Chem.SanitizeFlags.SANITIZE_PROPERTIES)

    m = Chem.MolFromSmiles('c1cccc1', sanitize=False)
    res = Chem.SanitizeMol(
      m, sanitizeOps=Chem.SanitizeFlags.SANITIZE_ALL ^ Chem.SanitizeFlags.SANITIZE_KEKULIZE,
      catchErrors=True)
    self.assertEqual(res, Chem.SanitizeFlags.SANITIZE_NONE)

  def test74Issue3510149(self):
    mol = Chem.MolFromSmiles("CCC1CNCC1CC")
    atoms = mol.GetAtoms()
    mol = None
    for atom in atoms:
      idx = atom.GetIdx()
      p = atom.GetOwningMol().GetNumAtoms()

    mol = Chem.MolFromSmiles("CCC1CNCC1CC")
    bonds = mol.GetBonds()
    mol = None
    for bond in bonds:
      idx = bond.GetIdx()
      p = atom.GetOwningMol().GetNumAtoms()

    mol = Chem.MolFromSmiles("CCC1CNCC1CC")
    bond = mol.GetBondBetweenAtoms(0, 1)
    mol = None
    idx = bond.GetBeginAtomIdx()
    p = bond.GetOwningMol().GetNumAtoms()

    fileN = os.path.join(RDConfig.RDBaseDir, 'Code', 'GraphMol', 'FileParsers', 'test_data',
                         'NCI_aids_few.sdf')
    sdSup = Chem.SDMolSupplier(fileN)
    mol = next(sdSup)
    nats = mol.GetNumAtoms()
    conf = mol.GetConformer()
    mol = None
    self.assertEqual(nats, conf.GetNumAtoms())
    conf.GetOwningMol().GetProp("_Name")

  def test75AllBondsExplicit(self):
    m = Chem.MolFromSmiles("CCC")
    smi = Chem.MolToSmiles(m)
    self.assertEqual(smi, "CCC")
    smi = Chem.MolToSmiles(m, allBondsExplicit=True)
    self.assertEqual(smi, "C-C-C")

    m = Chem.MolFromSmiles("c1ccccc1")
    smi = Chem.MolToSmiles(m)
    self.assertEqual(smi, "c1ccccc1")
    smi = Chem.MolToSmiles(m, allBondsExplicit=True)
    self.assertEqual(smi, "c1:c:c:c:c:c:1")

  def test76VeryLargeMolecule(self):
    # this is sf.net issue 3524984
    smi = '[C@H](F)(Cl)' + 'c1cc[nH]c1' * 500 + '[C@H](F)(Cl)'
    m = Chem.MolFromSmiles(smi)
    self.assertTrue(m)
    self.assertEqual(m.GetNumAtoms(), 2506)
    scs = Chem.FindMolChiralCenters(m)
    self.assertEqual(len(scs), 2)

  def test77MolFragmentToSmiles(self):
    smi = "OC1CC1CC"
    m = Chem.MolFromSmiles(smi)
    fsmi = Chem.MolFragmentToSmiles(m, [1, 2, 3])
    self.assertEqual(fsmi, "C1CC1")
    fsmi = Chem.MolFragmentToSmiles(m, [1, 2, 3], bondsToUse=[1, 2, 5])
    self.assertEqual(fsmi, "C1CC1")
    fsmi = Chem.MolFragmentToSmiles(m, [1, 2, 3], bondsToUse=[1, 2])
    self.assertEqual(fsmi, "CCC")
    fsmi = Chem.MolFragmentToSmiles(m, [1, 2, 3], atomSymbols=["", "[A]", "[C]", "[B]", "", ""])
    self.assertEqual(fsmi, "[A]1[B][C]1")
    fsmi = Chem.MolFragmentToSmiles(m, [1, 2, 3], bondSymbols=["", "%", "%", "", "", "%"])
    self.assertEqual(fsmi, "C1%C%C%1")

    smi = "c1ccccc1C"
    m = Chem.MolFromSmiles(smi)
    fsmi = Chem.MolFragmentToSmiles(m, range(6))
    self.assertEqual(fsmi, "c1ccccc1")
    Chem.Kekulize(m)
    fsmi = Chem.MolFragmentToSmiles(m, range(6), kekuleSmiles=True)
    self.assertEqual(fsmi, "C1=CC=CC=C1")
    fsmi = Chem.MolFragmentToSmiles(m, range(6), atomSymbols=["[C]"] * 7, kekuleSmiles=True)
    self.assertEqual(fsmi, "[C]1=[C][C]=[C][C]=[C]1")

    self.assertRaises(ValueError, lambda: Chem.MolFragmentToSmiles(m, []))

  def test78AtomAndBondProps(self):
    m = Chem.MolFromSmiles('c1ccccc1')
    at = m.GetAtomWithIdx(0)
    self.assertFalse(at.HasProp('foo'))
    at.SetProp('foo', 'bar')
    self.assertTrue(at.HasProp('foo'))
    self.assertEqual(at.GetProp('foo'), 'bar')
    bond = m.GetBondWithIdx(0)
    self.assertFalse(bond.HasProp('foo'))
    bond.SetProp('foo', 'bar')
    self.assertTrue(bond.HasProp('foo'))
    self.assertEqual(bond.GetProp('foo'), 'bar')

  def test79AddRecursiveStructureQueries(self):
    qs = {'carbonyl': Chem.MolFromSmiles('CO'), 'amine': Chem.MolFromSmiles('CN')}
    q = Chem.MolFromSmiles('CCC')
    q.GetAtomWithIdx(0).SetProp('query', 'carbonyl,amine')
    Chem.MolAddRecursiveQueries(q, qs, 'query')
    m = Chem.MolFromSmiles('CCCO')
    self.assertTrue(m.HasSubstructMatch(q))
    m = Chem.MolFromSmiles('CCCN')
    self.assertTrue(m.HasSubstructMatch(q))
    m = Chem.MolFromSmiles('CCCC')
    self.assertFalse(m.HasSubstructMatch(q))

  def test80ParseMolQueryDefFile(self):
    fileN = os.path.join(RDConfig.RDBaseDir, 'Code', 'GraphMol', 'ChemTransforms', 'testData',
                         'query_file1.txt')
    d = Chem.ParseMolQueryDefFile(fileN, standardize=False)
    self.assertTrue('CarboxylicAcid' in d)
    m = Chem.MolFromSmiles('CC(=O)O')
    self.assertTrue(m.HasSubstructMatch(d['CarboxylicAcid']))
    self.assertFalse(m.HasSubstructMatch(d['CarboxylicAcid.Aromatic']))

    d = Chem.ParseMolQueryDefFile(fileN)
    self.assertTrue('carboxylicacid' in d)
    self.assertFalse('CarboxylicAcid' in d)

  def test81Issue275(self):
    smi = Chem.MolToSmiles(Chem.MurckoDecompose(
      Chem.MolFromSmiles('CCCCC[C@H]1CC[C@H](C(=O)O)CC1')))
    self.assertEqual(smi, 'C1CCCCC1')

  def test82Issue288(self):
    m = Chem.MolFromSmiles('CC*')
    m.GetAtomWithIdx(2).SetProp('molAtomMapNumber', '30')

    smi = Chem.MolToSmiles(m)
    self.assertEqual(smi, 'CC[*:30]')
    # try newer api
    m = Chem.MolFromSmiles('CC*')
    m.GetAtomWithIdx(2).SetAtomMapNum(30)
    smi = Chem.MolToSmiles(m)
    self.assertEqual(smi, 'CC[*:30]')

  def test83GitHubIssue19(self):
    fileN = os.path.join(RDConfig.RDBaseDir, 'Code', 'GraphMol', 'FileParsers', 'test_data',
                         'empty2.sdf')
    with self.assertRaises(OSError):
      sdSup = Chem.SDMolSupplier(fileN)

    sdSup = Chem.SDMolSupplier()
    sdSup.SetData('')
    self.assertTrue(sdSup.atEnd())
    self.assertRaises(IndexError, lambda: sdSup[0])

    sdSup.SetData('')
    self.assertRaises(IndexError, lambda: sdSup[0])
    self.assertEqual(len(sdSup), 0)

  def test84PDBBasics(self):
    fileN = os.path.join(RDConfig.RDBaseDir, 'Code', 'GraphMol', 'FileParsers', 'test_data',
                         '1CRN.pdb')
    m = Chem.MolFromPDBFile(fileN, proximityBonding=False)
    self.assertEqual(m.GetNumAtoms(), 327)
    self.assertEqual(m.GetNumBonds(), 3)
    m = Chem.MolFromPDBFile(fileN)
    self.assertTrue(m is not None)
    self.assertEqual(m.GetNumAtoms(), 327)
    self.assertEqual(m.GetNumBonds(), 337)
    self.assertTrue(m.GetAtomWithIdx(0).GetPDBResidueInfo())
    self.assertEqual(m.GetAtomWithIdx(0).GetPDBResidueInfo().GetName(), " N  ")
    self.assertEqual(m.GetAtomWithIdx(0).GetPDBResidueInfo().GetResidueName(), "THR")
    self.assertAlmostEqual(m.GetAtomWithIdx(0).GetPDBResidueInfo().GetTempFactor(), 13.79, 2)
    m = Chem.MolFromPDBBlock(Chem.MolToPDBBlock(m))
    self.assertEqual(m.GetNumAtoms(), 327)
    self.assertEqual(m.GetNumBonds(), 337)
    self.assertTrue(m.GetAtomWithIdx(0).GetPDBResidueInfo())
    self.assertEqual(m.GetAtomWithIdx(0).GetPDBResidueInfo().GetName(), " N  ")
    self.assertEqual(m.GetAtomWithIdx(0).GetPDBResidueInfo().GetResidueName(), "THR")
    self.assertAlmostEqual(m.GetAtomWithIdx(0).GetPDBResidueInfo().GetTempFactor(), 13.79, 2)
    # test multivalent Hs
    fileN = os.path.join(RDConfig.RDBaseDir, 'Code', 'GraphMol', 'FileParsers', 'test_data',
                         '2c92_hypervalentH.pdb')
    mol = Chem.MolFromPDBFile(fileN, sanitize=False, removeHs=False)
    atom = mol.GetAtomWithIdx(84)
    self.assertEqual(atom.GetAtomicNum(), 1)  # is it H
    self.assertEqual(atom.GetDegree(), 1)  # H should have 1 bond
    for n in atom.GetNeighbors():  # Check if neighbor is from the same residue
      self.assertEqual(atom.GetPDBResidueInfo().GetResidueName(),
                       n.GetPDBResidueInfo().GetResidueName())
    # test unbinding metals (ZN)
    fileN = os.path.join(RDConfig.RDBaseDir, 'Code', 'GraphMol', 'FileParsers', 'test_data',
                         '1ps3_zn.pdb')
    mol = Chem.MolFromPDBFile(fileN, sanitize=False, removeHs=False)
    atom = mol.GetAtomWithIdx(40)
    self.assertEqual(atom.GetAtomicNum(), 30)  # is it Zn
    self.assertEqual(atom.GetDegree(), 4)  # Zn should have 4 zero-order bonds
    self.assertEqual(atom.GetExplicitValence(), 0)
    bonds_order = [bond.GetBondType() for bond in atom.GetBonds()]
    self.assertEqual(bonds_order, [Chem.BondType.ZERO] * atom.GetDegree())

    # test metal bonds without proximity bonding
    mol = Chem.MolFromPDBFile(fileN, sanitize=False, removeHs=False, proximityBonding=False)
    atom = mol.GetAtomWithIdx(40)
    self.assertEqual(atom.GetAtomicNum(), 30)  # is it Zn
    self.assertEqual(atom.GetDegree(), 4)  # Zn should have 4 zero-order bonds
    self.assertEqual(atom.GetExplicitValence(), 0)
    bonds_order = [bond.GetBondType() for bond in atom.GetBonds()]
    self.assertEqual(bonds_order, [Chem.BondType.ZERO] * atom.GetDegree())
    # test unbinding HOHs
    fileN = os.path.join(RDConfig.RDBaseDir, 'Code', 'GraphMol', 'FileParsers', 'test_data',
                         '2vnf_bindedHOH.pdb')
    mol = Chem.MolFromPDBFile(fileN, sanitize=False, removeHs=False)
    atom = mol.GetAtomWithIdx(10)
    self.assertEqual(atom.GetPDBResidueInfo().GetResidueName(), 'HOH')
    self.assertEqual(atom.GetDegree(), 0)  # HOH should have no bonds
    # test metal bonding in ligand
    fileN = os.path.join(RDConfig.RDBaseDir, 'Code', 'GraphMol', 'FileParsers', 'test_data',
                         '2dej_APW.pdb')
    mol = Chem.MolFromPDBFile(fileN, sanitize=False, removeHs=False)
    atom = mol.GetAtomWithIdx(6)
    self.assertEqual(atom.GetAtomicNum(), 12)
    self.assertEqual(atom.GetDegree(), 2)
    atom = mol.GetAtomWithIdx(35)
    self.assertEqual(atom.GetPDBResidueInfo().GetResidueName(), 'HOH')
    self.assertEqual(atom.GetDegree(), 0)

  def test85AtomCopying(self):
    """Can a copied atom be added to a molecule?"""
    import copy
    m = Chem.MolFromSmiles('C1CC1')
    a = m.GetAtomWithIdx(0)
    a_copy1 = copy.copy(a)
    a_copy2 = Chem.Atom(a)
    m = None
    a = None

    def assert_is_valid_atom(a):
      new_m = Chem.RWMol()
      new_m.AddAtom(a)
      # This will not match if the owning mol is unset for a_copy,
      # or if there has been a clean up.
      self.assertEqual(new_m.GetAtomWithIdx(0).GetIdx(), 0)

    assert_is_valid_atom(a_copy1)
    assert_is_valid_atom(a_copy2)

  def test85MolCopying(self):
    m = Chem.MolFromSmiles('C1CC1[C@H](F)Cl')
    m.SetProp('foo', 'bar')
    m2 = Chem.Mol(m)
    self.assertEqual(Chem.MolToSmiles(m, True), Chem.MolToSmiles(m2, True))
    self.assertTrue(m2.HasProp('foo'))
    self.assertEqual(m2.GetProp('foo'), 'bar')
    ri = m2.GetRingInfo()
    self.assertTrue(ri)
    self.assertEqual(ri.NumRings(), 1)

  def test85MolCopying2(self):
    import copy
    m1 = Chem.MolFromSmiles('CC')
    m1.SetProp('Foo', 'bar')
    m1.foo = [1]
    m2 = copy.copy(m1)
    m3 = copy.copy(m2)
    m4 = copy.deepcopy(m1)
    m5 = copy.deepcopy(m2)
    m6 = copy.deepcopy(m4)

    self.assertEqual(m1.GetProp('Foo'), 'bar')
    self.assertEqual(m2.GetProp('Foo'), 'bar')
    self.assertEqual(m3.GetProp('Foo'), 'bar')
    self.assertEqual(m4.GetProp('Foo'), 'bar')
    self.assertEqual(m5.GetProp('Foo'), 'bar')
    self.assertEqual(m6.GetProp('Foo'), 'bar')

    m2.foo.append(4)
    self.assertEqual(m1.foo, [1, 4])
    self.assertEqual(m2.foo, [1, 4])
    self.assertEqual(m3.foo, [1, 4])
    self.assertEqual(m4.foo, [1])
    self.assertEqual(m5.foo, [1])
    self.assertEqual(m6.foo, [1])

    m7 = Chem.RWMol(m1)
    self.assertFalse(hasattr(m7, 'foo'))
    m7.foo = [1]
    m8 = copy.copy(m7)
    m9 = copy.deepcopy(m7)
    m8.foo.append(4)
    self.assertEqual(m7.GetProp('Foo'), 'bar')
    self.assertEqual(m8.GetProp('Foo'), 'bar')
    self.assertEqual(m9.GetProp('Foo'), 'bar')
    self.assertEqual(m8.foo, [1, 4])
    self.assertEqual(m9.foo, [1])

  def test86MolRenumbering(self):
    import random
    m = Chem.MolFromSmiles('C[C@H]1CC[C@H](C/C=C/[C@H](F)Cl)CC1')
    cSmi = Chem.MolToSmiles(m, True)
    for i in range(m.GetNumAtoms()):
      ans = list(range(m.GetNumAtoms()))
      random.shuffle(ans)
      m2 = Chem.RenumberAtoms(m, ans)
      nSmi = Chem.MolToSmiles(m2, True)
      self.assertEqual(cSmi, nSmi)

  def test87FragmentOnBonds(self):
    m = Chem.MolFromSmiles('CC1CC(O)C1CCC1CC1')
    bis = m.GetSubstructMatches(Chem.MolFromSmarts('[!R][R]'))
    bs = []
    labels = []
    for bi in bis:
      b = m.GetBondBetweenAtoms(bi[0], bi[1])
      if b.GetBeginAtomIdx() == bi[0]:
        labels.append((10, 1))
      else:
        labels.append((1, 10))
      bs.append(b.GetIdx())
    nm = Chem.FragmentOnBonds(m, bs)
    frags = Chem.GetMolFrags(nm)
    self.assertEqual(len(frags), 5)
    self.assertEqual(frags,
                     ((0, 12), (1, 2, 3, 5, 11, 14, 16), (4, 13), (6, 7, 15, 18), (8, 9, 10, 17)))
    smi = Chem.MolToSmiles(nm, True)
    self.assertEqual(smi, '*C1CC([4*])C1[6*].[1*]C.[3*]O.[5*]CC[8*].[7*]C1CC1')

    nm = Chem.FragmentOnBonds(m, bs, dummyLabels=labels)
    frags = Chem.GetMolFrags(nm)
    self.assertEqual(len(frags), 5)
    self.assertEqual(frags,
                     ((0, 12), (1, 2, 3, 5, 11, 14, 16), (4, 13), (6, 7, 15, 18), (8, 9, 10, 17)))
    smi = Chem.MolToSmiles(nm, True)
    self.assertEqual(smi, '[1*]C.[1*]CC[1*].[1*]O.[10*]C1CC([10*])C1[10*].[10*]C1CC1')

    m = Chem.MolFromSmiles('CCC(=O)CC(=O)C')
    bis = m.GetSubstructMatches(Chem.MolFromSmarts('C=O'))
    bs = []
    for bi in bis:
      b = m.GetBondBetweenAtoms(bi[0], bi[1])
      bs.append(b.GetIdx())
    bts = [Chem.BondType.DOUBLE] * len(bs)
    nm = Chem.FragmentOnBonds(m, bs, bondTypes=bts)
    frags = Chem.GetMolFrags(nm)
    self.assertEqual(len(frags), 3)
    smi = Chem.MolToSmiles(nm, True)
    self.assertEqual(smi, '[2*]=O.[3*]=C(CC)CC(=[6*])C.[5*]=O')

    # github issue 430:
    m = Chem.MolFromSmiles('OCCCCN')
    self.assertRaises(ValueError, lambda: Chem.FragmentOnBonds(m, ()))

  def test88QueryAtoms(self):
    from rdkit.Chem import rdqueries
    m = Chem.MolFromSmiles('c1nc(C)n(CC)c1')

    qa = rdqueries.ExplicitDegreeEqualsQueryAtom(3)
    l = tuple([x.GetIdx() for x in m.GetAtomsMatchingQuery(qa)])
    self.assertEqual(l, (2, 4))

    qa.ExpandQuery(rdqueries.AtomNumEqualsQueryAtom(6, negate=True))
    l = tuple([x.GetIdx() for x in m.GetAtomsMatchingQuery(qa)])
    self.assertEqual(l, (4, ))

    qa = rdqueries.ExplicitDegreeEqualsQueryAtom(3)
    qa.ExpandQuery(rdqueries.AtomNumEqualsQueryAtom(6, negate=True),
                   how=Chem.CompositeQueryType.COMPOSITE_OR)
    l = tuple([x.GetIdx() for x in m.GetAtomsMatchingQuery(qa)])
    self.assertEqual(l, (1, 2, 4))

    qa = rdqueries.ExplicitDegreeEqualsQueryAtom(3)
    qa.ExpandQuery(rdqueries.AtomNumEqualsQueryAtom(6, negate=True),
                   how=Chem.CompositeQueryType.COMPOSITE_XOR)
    l = tuple([x.GetIdx() for x in m.GetAtomsMatchingQuery(qa)])
    self.assertEqual(l, (1, 2))

    qa = rdqueries.ExplicitDegreeGreaterQueryAtom(2)
    l = tuple([x.GetIdx() for x in m.GetAtomsMatchingQuery(qa)])
    self.assertEqual(l, (2, 4))

    qa = rdqueries.ExplicitDegreeLessQueryAtom(2)
    l = tuple([x.GetIdx() for x in m.GetAtomsMatchingQuery(qa)])
    self.assertEqual(l, (3, 6))

    m = Chem.MolFromSmiles('N[CH][CH]')
    qa = rdqueries.NumRadicalElectronsGreaterQueryAtom(0)
    l = tuple([x.GetIdx() for x in m.GetAtomsMatchingQuery(qa)])
    self.assertEqual(l, (1, 2))
    qa = rdqueries.NumRadicalElectronsGreaterQueryAtom(1)
    l = tuple([x.GetIdx() for x in m.GetAtomsMatchingQuery(qa)])
    self.assertEqual(l, (2, ))

    m = Chem.MolFromSmiles('F[C@H](Cl)C')
    qa = rdqueries.HasChiralTagQueryAtom()
    l = tuple([x.GetIdx() for x in m.GetAtomsMatchingQuery(qa)])
    self.assertEqual(l, (1, ))
    qa = rdqueries.MissingChiralTagQueryAtom()
    l = tuple([x.GetIdx() for x in m.GetAtomsMatchingQuery(qa)])
    self.assertEqual(l, ())

    m = Chem.MolFromSmiles('F[CH](Cl)C')
    qa = rdqueries.HasChiralTagQueryAtom()
    l = tuple([x.GetIdx() for x in m.GetAtomsMatchingQuery(qa)])
    self.assertEqual(l, ())
    qa = rdqueries.MissingChiralTagQueryAtom()
    l = tuple([x.GetIdx() for x in m.GetAtomsMatchingQuery(qa)])
    self.assertEqual(l, (1, ))

    m = Chem.MolFromSmiles('CNCON')
    qa = rdqueries.NumHeteroatomNeighborsEqualsQueryAtom(2)
    l = tuple([x.GetIdx() for x in m.GetAtomsMatchingQuery(qa)])
    self.assertEqual(l, (2, ))
    qa = rdqueries.NumHeteroatomNeighborsGreaterQueryAtom(0)
    l = tuple([x.GetIdx() for x in m.GetAtomsMatchingQuery(qa)])
    self.assertEqual(l, (0, 2, 3, 4))

    m = Chem.MolFromSmiles('CC12CCN(CC1)C2')
    qa = rdqueries.IsBridgeheadQueryAtom()
    l = tuple([x.GetIdx() for x in m.GetAtomsMatchingQuery(qa)])
    self.assertEqual(l, (1, 4))

    m = Chem.MolFromSmiles('OCCOC')
    qa = rdqueries.NonHydrogenDegreeEqualsQueryAtom(2)
    l = tuple([x.GetIdx() for x in m.GetAtomsMatchingQuery(qa)])
    self.assertEqual(l, (1, 2, 3))

  def test89UnicodeInput(self):
    m = Chem.MolFromSmiles(u'c1ccccc1')
    self.assertTrue(m is not None)
    self.assertEqual(m.GetNumAtoms(), 6)
    m = Chem.MolFromSmarts(u'c1ccccc1')
    self.assertTrue(m is not None)
    self.assertEqual(m.GetNumAtoms(), 6)

  def test90FragmentOnSomeBonds(self):
    m = Chem.MolFromSmiles('OCCCCN')
    pieces = Chem.FragmentOnSomeBonds(m, (0, 2, 4), 2)
    self.assertEqual(len(pieces), 3)

    frags = Chem.GetMolFrags(pieces[0])
    self.assertEqual(len(frags), 3)
    self.assertEqual(len(frags[0]), 2)
    self.assertEqual(len(frags[1]), 4)
    self.assertEqual(len(frags[2]), 4)

    frags = Chem.GetMolFrags(pieces[1])
    self.assertEqual(len(frags), 3)
    self.assertEqual(len(frags[0]), 2)
    self.assertEqual(len(frags[1]), 6)
    self.assertEqual(len(frags[2]), 2)

    frags = Chem.GetMolFrags(pieces[2])
    self.assertEqual(len(frags), 3)
    self.assertEqual(len(frags[0]), 4)
    self.assertEqual(len(frags[1]), 4)
    self.assertEqual(len(frags[2]), 2)

    pieces, cpa = Chem.FragmentOnSomeBonds(m, (0, 2, 4), 2, returnCutsPerAtom=True)
    self.assertEqual(len(pieces), 3)
    self.assertEqual(len(cpa), 3)
    self.assertEqual(len(cpa[0]), m.GetNumAtoms())

    # github issue 430:
    m = Chem.MolFromSmiles('OCCCCN')
    self.assertRaises(ValueError, lambda: Chem.FragmentOnSomeBonds(m, ()))

    pieces = Chem.FragmentOnSomeBonds(m, (0, 2, 4), 0)
    self.assertEqual(len(pieces), 0)

  def test91RankAtoms(self):
    m = Chem.MolFromSmiles('ONCS.ONCS')
    ranks = Chem.CanonicalRankAtoms(m, breakTies=False)
    self.assertEqual(list(ranks[0:4]), list(ranks[4:]))

    m = Chem.MolFromSmiles("c1ccccc1")
    ranks = Chem.CanonicalRankAtoms(m, breakTies=False)
    for x in ranks:
      self.assertEqual(x, 0)

    m = Chem.MolFromSmiles("C1NCN1")
    ranks = Chem.CanonicalRankAtoms(m, breakTies=False)
    self.assertEqual(ranks[0], ranks[2])
    self.assertEqual(ranks[1], ranks[3])

  def test92RankAtomsInFragment(self):
    m = Chem.MolFromSmiles('ONCS.ONCS')
    ranks = Chem.CanonicalRankAtomsInFragment(m, [0, 1, 2, 3], [0, 1, 2])

    ranks2 = Chem.CanonicalRankAtomsInFragment(m, [4, 5, 6, 7], [3, 4, 5])
    self.assertEqual(list(ranks[0:4]), list(ranks2[4:]))
    self.assertEqual(list(ranks[4:]), [-1] * 4)
    self.assertEqual(list(ranks2[0:4]), [-1] * 4)

    # doc tests
    mol = Chem.MolFromSmiles('C1NCN1.C1NCN1')
    self.assertEqual(
      list(Chem.CanonicalRankAtomsInFragment(mol, atomsToUse=range(0, 4), breakTies=False)),
      [4, 6, 4, 6, -1, -1, -1, -1])
    self.assertNotEqual(
      list(Chem.CanonicalRankAtomsInFragment(mol, atomsToUse=range(0, 4), breakTies=True)),
      [4, 6, 4, 6, -1, -1, -1, -1])
    self.assertEqual(
      list(Chem.CanonicalRankAtomsInFragment(mol, atomsToUse=range(4, 8), breakTies=False)),
      [-1, -1, -1, -1, 4, 6, 4, 6])
    self.assertNotEqual(
      list(Chem.CanonicalRankAtomsInFragment(mol, atomsToUse=range(4, 8), breakTies=True)),
      [-1, -1, -1, -1, 4, 6, 4, 6])

  def test93RWMolsAsROMol(self):
    """ test the RWMol class as a proper ROMol

    """
    mol = Chem.MolFromSmiles('C1CCC1')
    self.assertTrue(type(mol) == Chem.Mol)
    rwmol = Chem.RWMol(mol)
    self.assertEqual(Chem.MolToSmiles(rwmol, True), Chem.MolToSmiles(rwmol.GetMol()))
    newAt = Chem.Atom(8)
    rwmol.ReplaceAtom(0, newAt)
    self.assertEqual(Chem.MolToSmiles(rwmol, True), Chem.MolToSmiles(rwmol.GetMol()))

  def test94CopyWithConfs(self):
    """ test copying Mols with some conformers

    """
    fileN = os.path.join(RDConfig.RDBaseDir, 'Code', 'GraphMol', 'FileParsers', 'test_data',
                         'cmpd2.tpl')
    m1 = Chem.MolFromTPLFile(fileN)
    self.assertTrue(m1 is not None)
    self.assertEqual(m1.GetNumAtoms(), 12)
    self.assertEqual(m1.GetNumConformers(), 2)
    self.assertEqual(m1.GetConformer(0).GetNumAtoms(), 12)
    self.assertEqual(m1.GetConformer(1).GetNumAtoms(), 12)

    m2 = Chem.Mol(m1)
    self.assertEqual(m2.GetNumAtoms(), 12)
    self.assertEqual(m2.GetNumConformers(), 2)
    self.assertEqual(m2.GetConformer(0).GetNumAtoms(), 12)
    self.assertEqual(m2.GetConformer(1).GetNumAtoms(), 12)

    m2 = Chem.Mol(m1, False, 0)
    self.assertEqual(m2.GetNumAtoms(), 12)
    self.assertEqual(m2.GetNumConformers(), 1)
    self.assertEqual(m2.GetConformer(0).GetNumAtoms(), 12)

    m2 = Chem.Mol(m1, False, 1)
    self.assertEqual(m2.GetNumAtoms(), 12)
    self.assertEqual(m2.GetNumConformers(), 1)
    self.assertEqual(m2.GetConformer(1).GetNumAtoms(), 12)

    m2 = Chem.Mol(m1, True)
    self.assertTrue(m2.GetNumAtoms() == 12)
    self.assertTrue(m2.GetNumConformers() == 0)

    m2 = Chem.RWMol(m1)
    self.assertEqual(m2.GetNumAtoms(), 12)
    self.assertEqual(m2.GetNumConformers(), 2)
    self.assertEqual(m2.GetConformer(0).GetNumAtoms(), 12)
    self.assertEqual(m2.GetConformer(1).GetNumAtoms(), 12)

    m2 = Chem.RWMol(m1, False, 0)
    self.assertEqual(m2.GetNumAtoms(), 12)
    self.assertEqual(m2.GetNumConformers(), 1)
    self.assertEqual(m2.GetConformer(0).GetNumAtoms(), 12)

    m2 = Chem.RWMol(m1, False, 1)
    self.assertEqual(m2.GetNumAtoms(), 12)
    self.assertEqual(m2.GetNumConformers(), 1)
    self.assertEqual(m2.GetConformer(1).GetNumAtoms(), 12)

    m2 = Chem.RWMol(m1, True)
    self.assertTrue(m2.GetNumAtoms() == 12)
    self.assertTrue(m2.GetNumConformers() == 0)

  def testAtomPropQueries(self):
    """ test the property queries
    """
    from rdkit.Chem import rdqueries

    m = Chem.MolFromSmiles("C" * 14)
    atoms = m.GetAtoms()
    atoms[0].SetProp("hah", "hah")
    atoms[1].SetIntProp("bar", 1)
    atoms[2].SetIntProp("bar", 2)
    atoms[3].SetBoolProp("baz", True)
    atoms[4].SetBoolProp("baz", False)
    atoms[5].SetProp("boo", "hoo")
    atoms[6].SetProp("boo", "-urns")
    atoms[7].SetDoubleProp("boot", 1.0)
    atoms[8].SetDoubleProp("boot", 4.0)
    atoms[9].SetDoubleProp("number", 4.0)
    atoms[10].SetIntProp("number", 4)

    tests = ((rdqueries.HasIntPropWithValueQueryAtom, "bar", {
      1: [1],
      2: [2]
    }), (rdqueries.HasBoolPropWithValueQueryAtom, "baz", {
      True: [3],
      False: [4]
    }), (rdqueries.HasStringPropWithValueQueryAtom, "boo", {
      "hoo": [5],
      "-urns": [6]
    }), (rdqueries.HasDoublePropWithValueQueryAtom, "boot", {
      1.0: [7],
      4.0: [8]
    }))

    for query, name, lookups in tests:
      for t, v in lookups.items():
        q = query(name, t)
        self.assertEqual(v, [x.GetIdx() for x in m.GetAtomsMatchingQuery(q)])
        q = query(name, t, negate=True)
        self.assertEqual(sorted(set(range(14)) - set(v)),
                         [x.GetIdx() for x in m.GetAtomsMatchingQuery(q)])

    # check tolerances
    self.assertEqual([
      x.GetIdx() for x in m.GetAtomsMatchingQuery(
        rdqueries.HasDoublePropWithValueQueryAtom("boot", 1.0, tolerance=3.))
    ], [7, 8])

    # numbers are numbers?, i.e. int!=double
    self.assertEqual([
      x.GetIdx()
      for x in m.GetAtomsMatchingQuery(rdqueries.HasIntPropWithValueQueryAtom("number", 4))
    ], [10])

  def testBondPropQueries(self):
    """ test the property queries
    """
    from rdkit.Chem import rdqueries

    m = Chem.MolFromSmiles("C" * 14)
    bonds = m.GetBonds()
    bonds[0].SetProp("hah", "hah")
    bonds[1].SetIntProp("bar", 1)
    bonds[2].SetIntProp("bar", 2)
    bonds[3].SetBoolProp("baz", True)
    bonds[4].SetBoolProp("baz", False)
    bonds[5].SetProp("boo", "hoo")
    bonds[6].SetProp("boo", "-urns")
    bonds[7].SetDoubleProp("boot", 1.0)
    bonds[8].SetDoubleProp("boot", 4.0)
    bonds[9].SetDoubleProp("number", 4.0)
    bonds[10].SetIntProp("number", 4)

    tests = ((rdqueries.HasIntPropWithValueQueryBond, "bar", {
      1: [1],
      2: [2]
    }), (rdqueries.HasBoolPropWithValueQueryBond, "baz", {
      True: [3],
      False: [4]
    }), (rdqueries.HasStringPropWithValueQueryBond, "boo", {
      "hoo": [5],
      "-urns": [6]
    }), (rdqueries.HasDoublePropWithValueQueryBond, "boot", {
      1.0: [7],
      4.0: [8]
    }))

    for query, name, lookups in tests:
      for t, v in lookups.items():
        q = query(name, t)
        self.assertEqual(v, [x.GetIdx() for x in m.GetBonds() if q.Match(x)])
        q = query(name, t, negate=True)
        self.assertEqual(sorted(set(range(13)) - set(v)),
                         [x.GetIdx() for x in m.GetBonds() if q.Match(x)])

    # check tolerances
    q = rdqueries.HasDoublePropWithValueQueryBond("boot", 1.0, tolerance=3.)
    self.assertEqual([x.GetIdx() for x in m.GetBonds() if q.Match(x)], [7, 8])

    # numbers are numbers?, i.e. int!=double
    q = rdqueries.HasIntPropWithValueQueryBond("number", 4)
    self.assertEqual([x.GetIdx() for x in m.GetBonds() if q.Match(x)], [10])

  def testGetShortestPath(self):
    """ test the GetShortestPath() wrapper
    """
    smi = "CC(OC1C(CCCC3)C3C(CCCC2)C2C1OC(C)=O)=O"
    m = Chem.MolFromSmiles(smi)
    path = Chem.GetShortestPath(m, 1, 20)
    self.assertEqual(path, (1, 2, 3, 16, 17, 18, 20))

  def testGithub497(self):
    with tempfile.TemporaryFile() as tmp, gzip.open(tmp) as outf:
      with self.assertRaises(ValueError):
        w = Chem.SDWriter(outf)

  def testGithub498(self):
    if (sys.version_info < (3, 0)):
      mode = 'w+'
    else:
      mode = 'wt+'
    m = Chem.MolFromSmiles('C')
    with tempfile.NamedTemporaryFile() as tmp, gzip.open(tmp, mode) as outf:
      w = Chem.SDWriter(outf)
      w.write(m)
      w.close()

  def testReplaceBond(self):
    origmol = Chem.RWMol(Chem.MolFromSmiles("CC"))
    bonds = list(origmol.GetBonds())
    self.assertEqual(len(bonds), 1)
    singlebond = bonds[0]
    self.assertEqual(singlebond.GetBondType(), Chem.BondType.SINGLE)

    # this is the only way we create a bond, is take it from another molecule
    doublebonded = Chem.MolFromSmiles("C=C")
    doublebond = list(doublebonded.GetBonds())[0]

    # make sure replacing the bond changes the smiles
    self.assertEqual(Chem.MolToSmiles(origmol), "CC")
    origmol.ReplaceBond(singlebond.GetIdx(), doublebond)
    Chem.SanitizeMol(origmol)

    self.assertEqual(Chem.MolToSmiles(origmol), "C=C")

  def testAdjustQueryProperties(self):
    m = Chem.MolFromSmarts('C1CCC1*')
    am = Chem.AdjustQueryProperties(m)
    self.assertTrue(Chem.MolFromSmiles('C1CCC1C').HasSubstructMatch(m))
    self.assertTrue(Chem.MolFromSmiles('C1CCC1C').HasSubstructMatch(am))
    self.assertTrue(Chem.MolFromSmiles('C1CC(C)C1C').HasSubstructMatch(m))
    self.assertFalse(Chem.MolFromSmiles('C1CC(C)C1C').HasSubstructMatch(am))

    m = Chem.MolFromSmiles('C1CCC1*')
    am = Chem.AdjustQueryProperties(m)
    self.assertFalse(Chem.MolFromSmiles('C1CCC1C').HasSubstructMatch(m))
    self.assertTrue(Chem.MolFromSmiles('C1CCC1C').HasSubstructMatch(am))
    qps = Chem.AdjustQueryParameters()
    qps.makeDummiesQueries = False
    am = Chem.AdjustQueryProperties(m, qps)
    self.assertFalse(Chem.MolFromSmiles('C1CCC1C').HasSubstructMatch(am))

    m = Chem.MolFromSmiles('C1=CC=CC=C1', sanitize=False)
    am = Chem.AdjustQueryProperties(m)
    self.assertTrue(Chem.MolFromSmiles('c1ccccc1').HasSubstructMatch(am))
    qp = Chem.AdjustQueryParameters()
    qp.aromatizeIfPossible = False
    am = Chem.AdjustQueryProperties(m, qp)
    self.assertFalse(Chem.MolFromSmiles('c1ccccc1').HasSubstructMatch(am))

    m = Chem.MolFromSmiles('C1CCC1OC')
    qps = Chem.AdjustQueryParameters()
    qps.makeAtomsGeneric = True
    am = Chem.AdjustQueryProperties(m, qps)
    self.assertEqual(Chem.MolToSmarts(am), '*1-*-*-*-1-*-*')
    qps.makeAtomsGenericFlags = Chem.ADJUST_IGNORERINGS
    am = Chem.AdjustQueryProperties(m, qps)
    self.assertEqual(Chem.MolToSmarts(am), '[#6&D2]1-[#6&D2]-[#6&D2]-[#6&D3]-1-*-*')

    qps = Chem.AdjustQueryParameters()
    qps.makeBondsGeneric = True
    am = Chem.AdjustQueryProperties(m, qps)
    self.assertEqual(Chem.MolToSmarts(am), '[#6&D2]1~[#6&D2]~[#6&D2]~[#6&D3]~1~[#8]~[#6]')
    qps.makeBondsGenericFlags = Chem.ADJUST_IGNORERINGS
    am = Chem.AdjustQueryProperties(m, qps)
    self.assertEqual(Chem.MolToSmarts(am), '[#6&D2]1-[#6&D2]-[#6&D2]-[#6&D3]-1~[#8]~[#6]')

  def testMolFragmentSmarts(self):
    m = Chem.MolFromSmiles('C1CCC1OC')
    self.assertEqual(Chem.MolFragmentToSmarts(m, [0, 1, 2]), '[#6]-[#6]-[#6]')
    # if bondsToUse is honored, the ring won't show up
    self.assertEqual(Chem.MolFragmentToSmarts(m, [0, 1, 2, 3], bondsToUse=[0, 1, 2, 3]),
                     '[#6]-[#6]-[#6]-[#6]')

    # Does MolFragmentToSmarts accept output of AdjustQueryProperties?
    qps = Chem.AdjustQueryParameters()
    qps.makeAtomsGeneric = True
    am = Chem.AdjustQueryProperties(m, qps)
    self.assertEqual(Chem.MolFragmentToSmarts(am, [0, 1, 2]), '*-*-*')

  def testAdjustQueryPropertiesgithubIssue1474(self):
    core = Chem.MolFromSmiles('[*:1]C1N([*:2])C([*:3])O1')
    core.GetAtomWithIdx(0).SetProp('foo', 'bar')
    core.GetAtomWithIdx(1).SetProp('foo', 'bar')

    ap = Chem.AdjustQueryProperties(core)
    self.assertEqual(ap.GetAtomWithIdx(0).GetPropsAsDict()["foo"], "bar")
    self.assertEqual(ap.GetAtomWithIdx(1).GetPropsAsDict()["foo"], "bar")

  def testReplaceAtomWithQueryAtom(self):
    mol = Chem.MolFromSmiles("CC(C)C")
    qmol = Chem.MolFromSmiles("C")
    matches = mol.GetSubstructMatches(qmol)
    self.assertEqual(((0, ), (1, ), (2, ), (3, )), matches)

    atom = qmol.GetAtomWithIdx(0)
    natom = rdqueries.ReplaceAtomWithQueryAtom(qmol, atom)
    qa = rdqueries.ExplicitDegreeEqualsQueryAtom(3)
    natom.ExpandQuery(qa, Chem.CompositeQueryType.COMPOSITE_AND)
    matches = mol.GetSubstructMatches(qmol)
    self.assertEqual(((1, ), ), matches)

  def testGithubIssue579(self):
    fileN = os.path.join(RDConfig.RDBaseDir, 'Code', 'GraphMol', 'FileParsers', 'test_data',
                         'NCI_aids_few.sdf.gz')
    inf = gzip.open(fileN)
    suppl = Chem.ForwardSDMolSupplier(inf)
    m0 = next(suppl)
    self.assertIsNot(m0, None)
    inf.close()
    del suppl

  def testSequenceBasics(self):
    " very basic round-tripping of the sequence reader/writer support "
    helm = 'PEPTIDE1{C.Y.I.Q.N.C.P.L.G}$$$$'
    seq = 'CYIQNCPLG'
    fasta = '>\nCYIQNCPLG\n'
    smi = 'CC[C@H](C)[C@H](NC(=O)[C@H](Cc1ccc(O)cc1)NC(=O)[C@@H](N)CS)C(=O)N[C@@H](CCC(N)=O)C(=O)N[C@@H](CC(N)=O)C(=O)N[C@@H](CS)C(=O)N1CCC[C@H]1C(=O)N[C@@H](CC(C)C)C(=O)NCC(=O)O'

    m = Chem.MolFromSequence(seq)
    self.assertTrue(m is not None)
    self.assertEqual(Chem.MolToSequence(m), seq)
    self.assertEqual(Chem.MolToHELM(m), helm)
    self.assertEqual(Chem.MolToFASTA(m), fasta)
    self.assertEqual(Chem.MolToSmiles(m, isomericSmiles=True), smi)

    m = Chem.MolFromHELM(helm)
    self.assertTrue(m is not None)
    self.assertEqual(Chem.MolToSequence(m), seq)
    self.assertEqual(Chem.MolToHELM(m), helm)
    self.assertEqual(Chem.MolToFASTA(m), fasta)
    self.assertEqual(Chem.MolToSmiles(m, isomericSmiles=True), smi)

    m = Chem.MolFromFASTA(fasta)
    self.assertTrue(m is not None)
    self.assertEqual(Chem.MolToSequence(m), seq)
    self.assertEqual(Chem.MolToHELM(m), helm)
    self.assertEqual(Chem.MolToFASTA(m), fasta)
    self.assertEqual(Chem.MolToSmiles(m, isomericSmiles=True), smi)

    seq = "CGCGAATTACCGCG"
    m = Chem.MolFromSequence(seq, flavor=6)  # DNA
    self.assertEqual(Chem.MolToSequence(m), 'CGCGAATTACCGCG')
    self.assertEqual(
      Chem.MolToHELM(m),
      'RNA1{[dR](C)P.[dR](G)P.[dR](C)P.[dR](G)P.[dR](A)P.[dR](A)P.[dR](T)P.[dR](T)P.[dR](A)P.[dR](C)P.[dR](C)P.[dR](G)P.[dR](C)P.[dR](G)}$$$$'
    )
    seq = "CGCGAAUUACCGCG"
    m = Chem.MolFromSequence(seq, flavor=2)  # RNA
    self.assertEqual(Chem.MolToSequence(m), 'CGCGAAUUACCGCG')
    self.assertEqual(
      Chem.MolToHELM(m),
      'RNA1{R(C)P.R(G)P.R(C)P.R(G)P.R(A)P.R(A)P.R(U)P.R(U)P.R(A)P.R(C)P.R(C)P.R(G)P.R(C)P.R(G)}$$$$'
    )
    m = Chem.MolFromSequence(seq, flavor=3)  # RNA - 5' cap
    self.assertEqual(Chem.MolToSequence(m), 'CGCGAAUUACCGCG')
    self.assertEqual(
      Chem.MolToHELM(m),
      'RNA1{P.R(C)P.R(G)P.R(C)P.R(G)P.R(A)P.R(A)P.R(U)P.R(U)P.R(A)P.R(C)P.R(C)P.R(G)P.R(C)P.R(G)}$$$$'
    )

  def testResMolSupplier(self):
    mol = Chem.MolFromSmiles('CC')
    resMolSuppl = Chem.ResonanceMolSupplier(mol)
    del resMolSuppl
    resMolSuppl = Chem.ResonanceMolSupplier(mol)
    self.assertEqual(resMolSuppl.GetNumConjGrps(), 0)
    self.assertEqual(len(resMolSuppl), 1)
    self.assertEqual(resMolSuppl.GetNumConjGrps(), 0)

    mol = Chem.MolFromSmiles('NC(=[NH2+])c1ccc(cc1)C(=O)[O-]')
    totalFormalCharge = getTotalFormalCharge(mol)

    resMolSuppl = Chem.ResonanceMolSupplier(mol)
    self.assertFalse(resMolSuppl.GetIsEnumerated())
    self.assertEqual(len(resMolSuppl), 4)
    self.assertTrue(resMolSuppl.GetIsEnumerated())

    resMolSuppl = Chem.ResonanceMolSupplier(mol)
    self.assertFalse(resMolSuppl.GetIsEnumerated())
    resMolSuppl.Enumerate()
    self.assertTrue(resMolSuppl.GetIsEnumerated())
    self.assertTrue(
      (resMolSuppl[0].GetBondBetweenAtoms(0, 1).GetBondType() != resMolSuppl[1].GetBondBetweenAtoms(
        0, 1).GetBondType()) or (resMolSuppl[0].GetBondBetweenAtoms(9, 10).GetBondType()
                                 != resMolSuppl[1].GetBondBetweenAtoms(9, 10).GetBondType()))

    resMolSuppl = Chem.ResonanceMolSupplier(mol, Chem.KEKULE_ALL)
    self.assertEqual(len(resMolSuppl), 8)
    bondTypeSet = set()
    # check that we actually have two alternate Kekule structures
    bondTypeSet.add(resMolSuppl[0].GetBondBetweenAtoms(3, 4).GetBondType())
    bondTypeSet.add(resMolSuppl[1].GetBondBetweenAtoms(3, 4).GetBondType())
    self.assertEqual(len(bondTypeSet), 2)

    bondTypeDict = {}
    resMolSuppl = Chem.ResonanceMolSupplier(
      mol, Chem.ALLOW_INCOMPLETE_OCTETS
      | Chem.UNCONSTRAINED_CATIONS
      | Chem.UNCONSTRAINED_ANIONS)
    self.assertEqual(len(resMolSuppl), 32)
    for i in range(len(resMolSuppl)):
      resMol = resMolSuppl[i]
      self.assertEqual(getTotalFormalCharge(resMol), totalFormalCharge)
    while (not resMolSuppl.atEnd()):
      resMol = next(resMolSuppl)
      self.assertEqual(getTotalFormalCharge(resMol), totalFormalCharge)
    resMolSuppl.reset()
    cmpFormalChargeBondOrder(self, resMolSuppl[0], next(resMolSuppl))

    resMolSuppl = Chem.ResonanceMolSupplier(
      mol, Chem.ALLOW_INCOMPLETE_OCTETS
      | Chem.UNCONSTRAINED_CATIONS
      | Chem.UNCONSTRAINED_ANIONS, 10)
    self.assertEqual(len(resMolSuppl), 10)

    crambinPdb = os.path.join(RDConfig.RDBaseDir, 'Code', 'GraphMol', 'FileParsers', 'test_data',
                              '1CRN.pdb')
    mol = Chem.MolFromPDBFile(crambinPdb)
    resMolSuppl = Chem.ResonanceMolSupplier(mol)
    self.assertEqual(len(resMolSuppl), 1)
    resMolSuppl = Chem.ResonanceMolSupplier(mol, Chem.KEKULE_ALL)
    self.assertEqual(len(resMolSuppl), 8)

  def testGithub4884(self):
    # test that we don't hang
    mol = Chem.MolFromSmiles('O=[N+][O-]')
    supp = Chem.ResonanceMolSupplier(mol)
    supp.atEnd()

  def testSubstructMatchAcetate(self):
    mol = Chem.MolFromSmiles('CC(=O)[O-]')
    query = Chem.MolFromSmarts('C(=O)[O-]')

    resMolSuppl = Chem.ResonanceMolSupplier(mol)
    matches = mol.GetSubstructMatches(query)
    self.assertEqual(len(matches), 1)
    self.assertEqual(matches, ((1, 2, 3), ))
    matches = mol.GetSubstructMatches(query, uniquify=True)
    self.assertEqual(len(matches), 1)
    self.assertEqual(matches, ((1, 2, 3), ))
    matches = mol.GetSubstructMatches(query, uniquify=False)
    self.assertEqual(len(matches), 1)
    self.assertEqual(matches, ((1, 2, 3), ))
    matches = resMolSuppl.GetSubstructMatches(query)
    self.assertEqual(len(matches), 2)
    self.assertEqual(matches, ((1, 2, 3), (1, 3, 2)))
    matches = resMolSuppl.GetSubstructMatches(query, uniquify=True)
    self.assertEqual(len(matches), 1)
    self.assertEqual(matches, ((1, 2, 3), ))
    matches = resMolSuppl.GetSubstructMatches(query, uniquify=False)
    self.assertEqual(len(matches), 2)
    self.assertEqual(matches, ((1, 2, 3), (1, 3, 2)))
    query = Chem.MolFromSmarts('C(~O)~O')
    matches = mol.GetSubstructMatches(query, uniquify=False)
    self.assertEqual(len(matches), 2)
    self.assertEqual(matches, ((1, 2, 3), (1, 3, 2)))
    matches = mol.GetSubstructMatches(query, uniquify=True)
    self.assertEqual(len(matches), 1)
    self.assertEqual(matches, ((1, 2, 3), ))
    matches = resMolSuppl.GetSubstructMatches(query, uniquify=False)
    self.assertEqual(len(matches), 2)
    self.assertEqual(matches, ((1, 2, 3), (1, 3, 2)))
    matches = resMolSuppl.GetSubstructMatches(query, uniquify=True)
    self.assertEqual(len(matches), 1)
    self.assertEqual(matches, ((1, 2, 3), ))

  def testSubstructMatchDMAP(self):
    mol = Chem.MolFromSmiles('C(C)Nc1cc[nH+]cc1')
    query = Chem.MolFromSmarts('[#7+]')

    resMolSuppl = Chem.ResonanceMolSupplier(mol)
    matches = mol.GetSubstructMatches(query, False, False, False)
    self.assertEqual(len(matches), 1)
    p = matches[0]
    self.assertEqual(p[0], 6)
    matches = resMolSuppl.GetSubstructMatches(query, False, False, False)
    self.assertEqual(len(matches), 2)
    v = []
    p = matches[0]
    v.append(p[0])
    p = matches[1]
    v.append(p[0])
    v.sort()
    self.assertEqual(v[0], 2)
    self.assertEqual(v[1], 6)

  def testCrambin(self):
    crambinPdb = os.path.join(RDConfig.RDBaseDir, 'Code', 'GraphMol', 'FileParsers', 'test_data',
                              '1CRN.pdb')
    crambin = Chem.MolFromPDBFile(crambinPdb)
    res = []
    # protonate NH2
    res.append(Chem.MolFromSmarts('[Nh2][Ch;Ch2]'))
    # protonate Arg
    res.append(Chem.MolFromSmarts('[Nh][C]([Nh2])=[Nh]'))
    setResidueFormalCharge(crambin, res, 1)
    res = []
    # deprotonate COOH
    res.append(Chem.MolFromSmarts('C(=O)[Oh]'))
    setResidueFormalCharge(crambin, res, -1)
    res = []
    resMolSupplST = Chem.ResonanceMolSupplier(crambin)
    # crambin has 2 Arg (3 resonance structures each); 1 Asp, 1 Glu
    # and 1 terminal COO- (2 resonance structures each)
    # so possible resonance structures are 3^2 * 2^3 = 72
    self.assertEqual(len(resMolSupplST), 72)
    self.assertEqual(resMolSupplST.GetNumConjGrps(), 56)
    carboxylateQuery = Chem.MolFromSmarts('C(=O)[O-]')
    guanidiniumQuery = Chem.MolFromSmarts('NC(=[NH2+])N')
    matches = crambin.GetSubstructMatches(carboxylateQuery)
    self.assertEqual(len(matches), 3)
    matches = crambin.GetSubstructMatches(carboxylateQuery, uniquify=False)
    self.assertEqual(len(matches), 3)
    matches = crambin.GetSubstructMatches(guanidiniumQuery)
    self.assertEqual(len(matches), 0)
    matches = crambin.GetSubstructMatches(guanidiniumQuery, uniquify=False)
    self.assertEqual(len(matches), 0)
    matches = resMolSupplST.GetSubstructMatches(carboxylateQuery)
    self.assertEqual(len(matches), 6)
    self.assertEqual(matches, ((166, 167, 168), (166, 168, 167), (298, 299, 300), (298, 300, 299),
                               (320, 321, 326), (320, 326, 321)))
    matches = resMolSupplST.GetSubstructMatches(carboxylateQuery, uniquify=True)
    self.assertEqual(len(matches), 3)
    self.assertEqual(matches, ((166, 167, 168), (298, 299, 300), (320, 321, 326)))
    matches = resMolSupplST.GetSubstructMatches(guanidiniumQuery)
    self.assertEqual(len(matches), 8)
    self.assertEqual(matches, ((66, 67, 68, 69), (66, 67, 69, 68), (68, 67, 69, 66),
                               (69, 67, 68, 66), (123, 124, 125, 126), (123, 124, 126, 125),
                               (125, 124, 126, 123), (126, 124, 125, 123)))
    matches = resMolSupplST.GetSubstructMatches(guanidiniumQuery, uniquify=True)
    self.assertEqual(len(matches), 2)
    self.assertEqual(matches, ((66, 67, 68, 69), (123, 124, 125, 126)))
    btList2ST = getBtList2(resMolSupplST)
    self.assertTrue(btList2ST)
    resMolSupplMT = Chem.ResonanceMolSupplier(crambin)
    resMolSupplMT.SetNumThreads(0)
    self.assertEqual(len(resMolSupplST), len(resMolSupplMT))
    btList2MT = getBtList2(resMolSupplMT)
    self.assertTrue(btList2MT)
    self.assertEqual(len(btList2ST), len(btList2MT))
    for i in range(len(btList2ST)):
      for j in range(len(btList2ST)):
        self.assertEqual(btList2ST[i][j], btList2MT[i][j])
    for suppl in [resMolSupplST, resMolSupplMT]:
      matches = suppl.GetSubstructMatches(carboxylateQuery, numThreads=0)
      self.assertEqual(len(matches), 6)
      self.assertEqual(matches, ((166, 167, 168), (166, 168, 167), (298, 299, 300), (298, 300, 299),
                                 (320, 321, 326), (320, 326, 321)))
      matches = suppl.GetSubstructMatches(carboxylateQuery, uniquify=True, numThreads=0)
      self.assertEqual(len(matches), 3)
      self.assertEqual(matches, ((166, 167, 168), (298, 299, 300), (320, 321, 326)))
      matches = suppl.GetSubstructMatches(guanidiniumQuery, numThreads=0)
      self.assertEqual(len(matches), 8)
      self.assertEqual(matches, ((66, 67, 68, 69), (66, 67, 69, 68), (68, 67, 69, 66),
                                 (69, 67, 68, 66), (123, 124, 125, 126), (123, 124, 126, 125),
                                 (125, 124, 126, 123), (126, 124, 125, 123)))
      matches = suppl.GetSubstructMatches(guanidiniumQuery, uniquify=True, numThreads=0)
      self.assertEqual(len(matches), 2)
      self.assertEqual(matches, ((66, 67, 68, 69), (123, 124, 125, 126)))

  def testGitHub1166(self):
    mol = Chem.MolFromSmiles('NC(=[NH2+])c1ccc(cc1)C(=O)[O-]')
    resMolSuppl = Chem.ResonanceMolSupplier(mol, Chem.KEKULE_ALL)
    self.assertEqual(len(resMolSuppl), 8)
    # check that formal charges on odd indices are in the same position
    # as on even indices
    for i in range(0, len(resMolSuppl), 2):
      self.assertEqual(resMolSuppl[i].GetNumAtoms(), resMolSuppl[i + 1].GetNumAtoms())
      for atomIdx in range(resMolSuppl[i].GetNumAtoms()):
        self.assertEqual(resMolSuppl[i].GetAtomWithIdx(atomIdx).GetFormalCharge(),
                         resMolSuppl[i + 1].GetAtomWithIdx(atomIdx).GetFormalCharge())
      # check that bond orders are alternate on aromatic bonds between
      # structures on odd indices and structures on even indices
      self.assertEqual(resMolSuppl[i].GetNumBonds(), resMolSuppl[i + 1].GetNumBonds())
      for bondIdx in range(resMolSuppl[i].GetNumBonds()):
        self.assertTrue(
          ((not resMolSuppl[i].GetBondWithIdx(bondIdx).GetIsAromatic()) and
           (not resMolSuppl[i + 1].GetBondWithIdx(bondIdx).GetIsAromatic()) and
           (resMolSuppl[i].GetBondWithIdx(bondIdx).GetBondType()
            == resMolSuppl[i + 1].GetBondWithIdx(bondIdx).GetBondType()))
          or (resMolSuppl[i].GetBondWithIdx(bondIdx).GetIsAromatic()
              and resMolSuppl[i + 1].GetBondWithIdx(bondIdx).GetIsAromatic() and (int(
                round(resMolSuppl[i].GetBondWithIdx(bondIdx).GetBondTypeAsDouble() +
                      resMolSuppl[i + 1].GetBondWithIdx(bondIdx).GetBondTypeAsDouble())) == 3)))

  def testConjGrpPerception(self):
    mol1 = Chem.MolFromMolBlock("""\

     RDKit          2D

 14 15  0  0  0  0  0  0  0  0999 V2000
    3.7539   -1.2744    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
    2.4317   -0.5660    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
    1.1571   -1.3568    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
   -0.1651   -0.6484    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
   -1.4397   -1.4393    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
   -1.3921   -2.9385    0.0000 F   0  0  0  0  0  0  0  0  0  0  0  0
   -2.7619   -0.7309    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
   -2.8095    0.7684    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
   -4.1316    1.4768    0.0000 F   0  0  0  0  0  0  0  0  0  0  0  0
   -1.5349    1.5592    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
   -0.2127    0.8508    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
    1.0619    1.6417    0.0000 N   0  0  0  0  0  0  0  0  0  0  0  0
    2.3841    0.9333    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
    3.6587    1.7241    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
  1  2  1  0
  2  3  4  0
  3  4  4  0
  4  5  4  0
  5  6  1  0
  5  7  4  0
  7  8  4  0
  8  9  1  0
  8 10  4  0
 10 11  4  0
 11 12  4  0
 12 13  4  0
 13 14  1  0
 13  2  4  0
 11  4  4  0
M  END
$$$$
""")
    mol2 = Chem.MolFromMolBlock("""\

     RDKit          2D

 14 15  0  0  0  0  0  0  0  0999 V2000
    1.0619   -1.6417    0.0000 N   0  0  0  0  0  0  0  0  0  0  0  0
   -0.2127   -0.8508    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
   -1.5349   -1.5592    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
   -2.8095   -0.7684    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
   -2.7619    0.7309    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
   -1.4397    1.4393    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
   -0.1651    0.6484    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
    1.1571    1.3568    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
    2.4317    0.5660    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
    3.7539    1.2744    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
    2.3841   -0.9333    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
    3.6587   -1.7241    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
   -4.1316   -1.4768    0.0000 F   0  0  0  0  0  0  0  0  0  0  0  0
   -1.3921    2.9385    0.0000 F   0  0  0  0  0  0  0  0  0  0  0  0
  1  2  4  0
  3  4  4  0
  4  5  4  0
  5  6  4  0
  2  3  4  0
  2  7  4  0
  7  8  4  0
  8  9  4  0
  9 10  1  0
  9 11  4  0
 11 12  1  0
 11  1  4  0
  6  7  4  0
  4 13  1  0
  6 14  1  0
M  END
$$$$
""")
    resMolSuppl1 = Chem.ResonanceMolSupplier(mol1, Chem.KEKULE_ALL)
    self.assertEqual(len(resMolSuppl1), 3)
    resMolSuppl2 = Chem.ResonanceMolSupplier(mol2, Chem.KEKULE_ALL)
    self.assertEqual(len(resMolSuppl2), 3)

  def testGitHub2597(self):

    class MyBrokenCallBack(Chem.ResonanceMolSupplier):

      def __call__(self):
        return True

    class MyBrokenCallBack2(Chem.ResonanceMolSupplierCallback):
      pass

    class ExceedNumStructures(Chem.ResonanceMolSupplierCallback):

      def __init__(self, parent):
        super().__init__()
        self._parent = parent

      def __call__(self):
        self._parent.assertEqual(self.GetNumConjGrps(), 1)
        return (self.GetNumStructures(0) < 12)

    class ExceedNumDiverseStructures(Chem.ResonanceMolSupplierCallback):

      def __init__(self, parent):
        super().__init__()
        self._parent = parent

      def __call__(self):
        self._parent.assertEqual(self.GetNumConjGrps(), 1)
        return (self.GetNumDiverseStructures(0) < 8)

    class ExceedTimeout(Chem.ResonanceMolSupplierCallback):

      def __init__(self, parent):
        super().__init__()
        self.start_time = None
        self.timeout = timedelta(seconds=3)
        self._parent = parent

      def __call__(self):
        if (self.start_time is None):
          self.start_time = datetime.now()
        return (datetime.now() - self.start_time < self.timeout)

    mol = Chem.MolFromSmiles(
      "ClC1=NC(NC2=CC=CC3=C2C(=O)C2=CC=CC=C2C3=O)=NC(NC2=CC=CC3=C2C(=O)C2=CC=CC=C2C3=O)=N1")
    resMolSuppl = Chem.ResonanceMolSupplier(mol)
    self.assertEqual(len(resMolSuppl), 1)
    resMolSuppl = Chem.ResonanceMolSupplier(mol, Chem.KEKULE_ALL)

    self.assertEqual(len(resMolSuppl), 32)
    resMolSuppl = Chem.ResonanceMolSupplier(mol, Chem.ALLOW_CHARGE_SEPARATION, 10)
    self.assertEqual(len(resMolSuppl), 10)
    self.assertFalse(resMolSuppl.WasCanceled())
    resMolSuppl = Chem.ResonanceMolSupplier(mol, Chem.ALLOW_CHARGE_SEPARATION)
    callback = resMolSuppl.GetProgressCallback()
    self.assertIsNone(callback)
    resMolSuppl.SetProgressCallback(ExceedNumStructures(self))
    callback = resMolSuppl.GetProgressCallback()
    self.assertTrue(isinstance(callback, ExceedNumStructures))
    resMolSuppl.SetProgressCallback(None)
    callback = resMolSuppl.GetProgressCallback()
    self.assertIsNone(callback)
    resMolSuppl.SetProgressCallback(ExceedNumStructures(self))
    self.assertEqual(len(resMolSuppl), 12)
    self.assertTrue(resMolSuppl.WasCanceled())
    resMolSuppl = Chem.ResonanceMolSupplier(mol, Chem.ALLOW_CHARGE_SEPARATION)
    with self.assertRaises(TypeError):
      resMolSuppl.SetProgressCallback(MyBrokenCallBack())
    with self.assertRaises(AttributeError):
      resMolSuppl.SetProgressCallback(MyBrokenCallBack2())
    resMolSuppl.SetProgressCallback(ExceedNumDiverseStructures(self))
    self.assertEqual(len(resMolSuppl), 9)
    self.assertTrue(resMolSuppl.WasCanceled())
    resMolSuppl = Chem.ResonanceMolSupplier(
      mol, Chem.UNCONSTRAINED_CATIONS | Chem.UNCONSTRAINED_ANIONS | Chem.KEKULE_ALL)
    resMolSuppl.SetProgressCallback(ExceedTimeout(self))
    resMolSuppl.Enumerate()
    print(len(resMolSuppl))
    self.assertTrue(resMolSuppl.WasCanceled())

  def testAtomBondProps(self):
    m = Chem.MolFromSmiles('c1ccccc1')
    for atom in m.GetAtoms():
      d = atom.GetPropsAsDict()
      self.assertEqual(set(d.keys()), set(['_CIPRank', '__computedProps']))
      self.assertEqual(d['_CIPRank'], 0)
      self.assertEqual(list(d['__computedProps']), ['_CIPRank'])

    for bond in m.GetBonds():
      self.assertEqual(bond.GetPropsAsDict(), {})

  def testSDProps(self):
    fileN = os.path.join(RDConfig.RDBaseDir, 'Code', 'GraphMol', 'FileParsers', 'test_data',
                         'NCI_aids_few.sdf')
    #fileN = "../FileParsers/test_data/NCI_aids_few.sdf"
    sddata = [
      {
        '_MolFileInfo': 'BBtclserve11129916382D 0   0.00000     0.00000    48',
        'NSC': 48,
        'NCI_AIDS_Antiviral_Screen_IC50': '2.00E-04\tM\t=\t2.46E-05\t3',
        '_Name': 48,
        'CAS_RN': '15716-70-8',
        '_MolFileChiralFlag': 0,
        '_MolFileComments': '15716-70-8',
        'NCI_AIDS_Antiviral_Screen_EC50': '2.00E-04\tM\t>\t2.00E-04\t3',
        'NCI_AIDS_Antiviral_Screen_Conclusion': 'CI'
      },
      {
        '_MolFileInfo': 'BBtclserve11129916382D 0   0.00000     0.00000    78',
        'NSC': 78,
        'NCI_AIDS_Antiviral_Screen_IC50': '2.00E-04\tM\t=\t9.80E-05\t3',
        '_Name': 78,
        'CAS_RN': '6290-84-2',
        '_MolFileChiralFlag': 0,
        '_MolFileComments': '6290-84-2',
        'NCI_AIDS_Antiviral_Screen_EC50': '2.00E-04\tM\t>\t2.00E-04\t3',
        'NCI_AIDS_Antiviral_Screen_Conclusion': 'CI'
      },
      {
        '_MolFileInfo': 'BBtclserve11129916382D 0   0.00000     0.00000   128',
        'NSC': 128,
        'NCI_AIDS_Antiviral_Screen_IC50': '2.00E-04\tM\t=\t4.60E-05\t4',
        '_Name': 128,
        'CAS_RN': '5395-10-8',
        '_MolFileChiralFlag': 0,
        '_MolFileComments': '5395-10-8',
        'NCI_AIDS_Antiviral_Screen_EC50': '2.00E-04\tM\t>\t2.00E-04\t4',
        'NCI_AIDS_Antiviral_Screen_Conclusion': 'CI'
      },
      {
        '_MolFileInfo': 'BBtclserve11129916382D 0   0.00000     0.00000   163',
        'NSC': 163,
        'NCI_AIDS_Antiviral_Screen_IC50': '6.75E-04\tM\t>\t6.75E-04\t2',
        '_Name': 163,
        'CAS_RN': '81-11-8',
        '_MolFileChiralFlag': 0,
        '_MolFileComments': '81-11-8',
        'NCI_AIDS_Antiviral_Screen_EC50': '6.75E-04\tM\t>\t6.75E-04\t2',
        'NCI_AIDS_Antiviral_Screen_Conclusion': 'CI'
      },
      {
        '_MolFileInfo': 'BBtclserve11129916382D 0   0.00000     0.00000   164',
        'NSC': 164,
        'NCI_AIDS_Antiviral_Screen_IC50': '2.00E-04\tM\t>\t2.00E-04\t2',
        '_Name': 164,
        'CAS_RN': '5325-43-9',
        '_MolFileChiralFlag': 0,
        '_MolFileComments': '5325-43-9',
        'NCI_AIDS_Antiviral_Screen_EC50': '2.00E-04\tM\t>\t2.00E-04\t2',
        'NCI_AIDS_Antiviral_Screen_Conclusion': 'CI'
      },
      {
        '_MolFileInfo': 'BBtclserve11129916382D 0   0.00000     0.00000   170',
        'NSC': 170,
        '_Name': 170,
        'CAS_RN': '999-99-9',
        '_MolFileChiralFlag': 0,
        '_MolFileComments': '999-99-9',
        'NCI_AIDS_Antiviral_Screen_EC50': '9.47E-04\tM\t>\t9.47E-04\t1',
        'NCI_AIDS_Antiviral_Screen_Conclusion': 'CI'
      },
      {
        '_MolFileInfo': 'BBtclserve11129916382D 0   0.00000     0.00000   180',
        'NSC': 180,
        'NCI_AIDS_Antiviral_Screen_IC50':
        '6.46E-04\tM\t=\t5.80E-04\t2\n1.81E-03\tM\t=\t6.90E-04\t2',
        '_Name': 180,
        'CAS_RN': '69-72-7',
        '_MolFileChiralFlag': 0,
        '_MolFileComments': '69-72-7',
        'NCI_AIDS_Antiviral_Screen_EC50':
        '6.46E-04\tM\t>\t6.46E-04\t2\n1.81E-03\tM\t>\t1.81E-03\t2',
        'NCI_AIDS_Antiviral_Screen_Conclusion': 'CI'
      },
      {
        '_MolFileInfo': 'BBtclserve11129916382D 0   0.00000     0.00000   186',
        'NSC': 186,
        'NCI_AIDS_Antiviral_Screen_IC50': '1.44E-04\tM\t=\t2.49E-05\t2',
        '_Name': 186,
        'CAS_RN': '518-75-2',
        '_MolFileChiralFlag': 0,
        '_MolFileComments': '518-75-2',
        'NCI_AIDS_Antiviral_Screen_EC50': '1.44E-04\tM\t>\t1.44E-04\t2',
        'NCI_AIDS_Antiviral_Screen_Conclusion': 'CI'
      },
      {
        '_MolFileInfo': 'BBtclserve11129916382D 0   0.00000     0.00000   192',
        'NSC': 192,
        'NCI_AIDS_Antiviral_Screen_IC50': '2.00E-04\tM\t=\t3.38E-06\t2',
        '_Name': 192,
        'CAS_RN': '2217-55-2',
        '_MolFileChiralFlag': 0,
        '_MolFileComments': '2217-55-2',
        'NCI_AIDS_Antiviral_Screen_EC50': '2.00E-04\tM\t>\t2.00E-04\t2',
        'NCI_AIDS_Antiviral_Screen_Conclusion': 'CI'
      },
      {
        '_MolFileInfo': 'BBtclserve11129916382D 0   0.00000     0.00000   203',
        'NSC': 203,
        '_Name': 203,
        'CAS_RN': '1155-00-6',
        '_MolFileChiralFlag': 0,
        '_MolFileComments': '1155-00-6',
        'NCI_AIDS_Antiviral_Screen_Conclusion': 'CI'
      },
      {
        '_MolFileInfo': 'BBtclserve11129916382D 0   0.00000     0.00000   210',
        'NSC': 210,
        'NCI_AIDS_Antiviral_Screen_IC50': '1.33E-03\tM\t>\t1.33E-03\t2',
        '_Name': 210,
        'CAS_RN': '5325-75-7',
        '_MolFileChiralFlag': 0,
        '_MolFileComments': '5325-75-7',
        'NCI_AIDS_Antiviral_Screen_EC50': '1.33E-03\tM\t>\t1.33E-03\t2',
        'NCI_AIDS_Antiviral_Screen_Conclusion': 'CI'
      },
      {
        '_MolFileInfo': 'BBtclserve11129916382D 0   0.00000     0.00000   211',
        'NSC': 211,
        'NCI_AIDS_Antiviral_Screen_IC50':
        '2.00E-04\tM\t>\t2.00E-04\t8\n2.00E-03\tM\t=\t1.12E-03\t2',
        '_Name': 211,
        'CAS_RN': '5325-76-8',
        '_MolFileChiralFlag': 0,
        '_MolFileComments': '5325-76-8',
        'NCI_AIDS_Antiviral_Screen_EC50':
        '2.00E-04\tM\t>\t7.42E-05\t8\n2.00E-03\tM\t=\t6.35E-05\t2',
        'NCI_AIDS_Antiviral_Screen_Conclusion': 'CM'
      },
      {
        '_MolFileInfo': 'BBtclserve11129916382D 0   0.00000     0.00000   213',
        'NSC': 213,
        'NCI_AIDS_Antiviral_Screen_IC50': '2.00E-04\tM\t>\t2.00E-04\t4',
        '_Name': 213,
        'CAS_RN': '119-80-2',
        '_MolFileChiralFlag': 0,
        '_MolFileComments': '119-80-2',
        'NCI_AIDS_Antiviral_Screen_EC50': '2.00E-04\tM\t>\t2.00E-04\t4',
        'NCI_AIDS_Antiviral_Screen_Conclusion': 'CI'
      },
      {
        '_MolFileInfo': 'BBtclserve11129916382D 0   0.00000     0.00000   220',
        'NSC': 220,
        'NCI_AIDS_Antiviral_Screen_IC50': '2.00E-04\tM\t>\t2.00E-04\t4',
        '_Name': 220,
        'CAS_RN': '5325-83-7',
        '_MolFileChiralFlag': 0,
        '_MolFileComments': '5325-83-7',
        'NCI_AIDS_Antiviral_Screen_EC50': '2.00E-04\tM\t>\t2.00E-04\t4',
        'NCI_AIDS_Antiviral_Screen_Conclusion': 'CI'
      },
      {
        '_MolFileInfo': 'BBtclserve11129916382D 0   0.00000     0.00000   229',
        'NSC': 229,
        'NCI_AIDS_Antiviral_Screen_IC50': '2.00E-04\tM\t>\t2.00E-04\t2',
        '_Name': 229,
        'CAS_RN': '5325-88-2',
        '_MolFileChiralFlag': 0,
        '_MolFileComments': '5325-88-2',
        'NCI_AIDS_Antiviral_Screen_EC50': '2.00E-04\tM\t>\t2.00E-04\t2',
        'NCI_AIDS_Antiviral_Screen_Conclusion': 'CI'
      },
      {
        '_MolFileInfo': 'BBtclserve11129916382D 0   0.00000     0.00000   256',
        'NSC': 256,
        'NCI_AIDS_Antiviral_Screen_IC50': '2.00E-04\tM\t>\t2.00E-04\t4',
        '_Name': 256,
        'CAS_RN': '5326-06-7',
        '_MolFileChiralFlag': 0,
        '_MolFileComments': '5326-06-7',
        'NCI_AIDS_Antiviral_Screen_EC50': '2.00E-04\tM\t>\t2.00E-04\t4',
        'NCI_AIDS_Antiviral_Screen_Conclusion': 'CI'
      },
    ]
    sdSup = Chem.SDMolSupplier(fileN)
    for i, mol in enumerate(sdSup):
      self.assertEqual(mol.GetPropsAsDict(includePrivate=True), sddata[i])

  def testGetSetProps(self):
    m = Chem.MolFromSmiles("CC")
    errors = {
      "int":
      r"key `foo` exists but does not result in an integer value reason: [B,b]ad any[\ ,_]cast",
      "uint overflow":
      "key `foo` exists but does not result in an unsigned integer value reason: bad numeric conversion: negative overflow",
      "int overflow":
      "key `foo` exists but does not result in an integer value reason: bad numeric conversion: positive overflow",
      "double":
      r"key `foo` exists but does not result in a double value reason: [B,b]ad any[\ ,_]cast",
      "bool":
      r"key `foo` exists but does not result in a True or False value reason: [B,b]ad any[\ ,_]cast"
    }

    for ob in [m, list(m.GetAtoms())[0], list(m.GetBonds())[0]]:
      ob.SetDoubleProp("foo", 2.0)
      with self.assertRaises(ValueError) as e:
        ob.GetBoolProp("foo")
      self.assertRegex(str(e.exception), errors["bool"])

      with self.assertRaises(ValueError) as e:
        ob.GetIntProp("foo")
      self.assertRegex(str(e.exception), errors["int"])

      ob.SetBoolProp("foo", True)
      with self.assertRaises(ValueError) as e:
        ob.GetDoubleProp("foo")
      self.assertRegex(str(e.exception), errors["double"])

      with self.assertRaises(ValueError) as e:
        ob.GetIntProp("foo")
      self.assertRegex(str(e.exception), errors["int"])

      ob.SetIntProp("foo", -1)
      with self.assertRaises(ValueError) as e:
        ob.GetUnsignedProp("foo")
      self.assertEqual(str(e.exception), errors["uint overflow"])

      ob.SetUnsignedProp("foo", 4294967295)
      self.assertEqual(ob.GetUnsignedProp("foo"), 4294967295)
      with self.assertRaises(ValueError) as e:
        ob.GetIntProp("foo")
      self.assertEqual(str(e.exception), errors["int overflow"])

  def testInvariantException(self):
    m = Chem.MolFromSmiles("C")
    try:
      m.GetAtomWithIdx(3)
    except RuntimeError as e:
      import platform
      details = str(e)
      if platform.system() == 'Windows':
        details = details.replace('\\', '/')
      self.assertTrue("Code/GraphMol/ROMol.cpp".lower() in details.lower())
      self.assertTrue("Failed Expression: 3 < 1" in details)
      self.assertTrue("RDKIT:" in details)
      self.assertTrue(__version__ in details)

  def testGetSDText(self):
    fileN = os.path.join(RDConfig.RDBaseDir, 'Code', 'GraphMol', 'FileParsers', 'test_data',
                         'NCI_aids_few.sdf')
    #fileN = "../FileParsers/test_data/NCI_aids_few.sdf"
    sdSup = Chem.SDMolSupplier(fileN)
    for m in sdSup:
      sdt = Chem.SDWriter.GetText(m)
      ts = Chem.SDMolSupplier()
      ts.SetData(sdt)
      nm = next(ts)
      self.assertEqual(Chem.MolToSmiles(m, True), Chem.MolToSmiles(nm, True))
      for pn in m.GetPropNames():
        self.assertTrue(nm.HasProp(pn))
        self.assertEqual(m.GetProp(pn), nm.GetProp(pn))

  def testUnfoldedRDKFingerprint(self):
    from rdkit.Chem import AllChem

    m = Chem.MolFromSmiles('c1ccccc1N')
    fp = AllChem.UnfoldedRDKFingerprintCountBased(m)
    fpDict = fp.GetNonzeroElements()
    self.assertEqual(len(fpDict.items()), 19)
    self.assertTrue(374073638 in fpDict)
    self.assertEqual(fpDict[374073638], 6)
    self.assertTrue(464351883 in fpDict)
    self.assertEqual(fpDict[464351883], 2)
    self.assertTrue(1949583554 in fpDict)
    self.assertEqual(fpDict[1949583554], 6)
    self.assertTrue(4105342207 in fpDict)
    self.assertEqual(fpDict[4105342207], 1)
    self.assertTrue(794080973 in fpDict)
    self.assertEqual(fpDict[794080973], 1)
    self.assertTrue(3826517238 in fpDict)
    self.assertEqual(fpDict[3826517238], 2)

    m = Chem.MolFromSmiles('Cl')
    fp = AllChem.UnfoldedRDKFingerprintCountBased(m)
    fpDict = fp.GetNonzeroElements()
    self.assertEqual(len(fpDict.items()), 0)

    m = Chem.MolFromSmiles('CCCO')
    aBits = {}
    fp = AllChem.UnfoldedRDKFingerprintCountBased(m, bitInfo=aBits)
    fpDict = fp.GetNonzeroElements()
    self.assertEqual(len(fpDict.items()), 5)
    self.assertTrue(1524090560 in fpDict)
    self.assertEqual(fpDict[1524090560], 1)
    self.assertTrue(1940446997 in fpDict)
    self.assertEqual(fpDict[1940446997], 1)
    self.assertTrue(3977409745 in fpDict)
    self.assertEqual(fpDict[3977409745], 1)
    self.assertTrue(4274652475 in fpDict)
    self.assertEqual(fpDict[4274652475], 1)
    self.assertTrue(4275705116 in fpDict)
    self.assertEqual(fpDict[4275705116], 2)

    self.assertTrue(1524090560 in aBits)
    self.assertEqual(aBits[1524090560], [[1, 2]])
    self.assertTrue(1940446997 in aBits)
    self.assertEqual(aBits[1940446997], [[0, 1]])
    self.assertTrue(3977409745 in aBits)
    self.assertEqual(aBits[3977409745], [[0, 1, 2]])
    self.assertTrue(4274652475 in aBits)
    self.assertEqual(aBits[4274652475], [[2]])
    self.assertTrue(4275705116 in aBits)
    self.assertEqual(aBits[4275705116], [[0], [1]])

  def testRDKFingerprintBitInfo(self):

    m = Chem.MolFromSmiles('CCCO')
    aBits = {}
    fp1 = Chem.RDKFingerprint(m, bitInfo=aBits)
    self.assertTrue(1183 in aBits)
    self.assertEqual(aBits[1183], [[1, 2]])
    self.assertTrue(709 in aBits)
    self.assertEqual(aBits[709], [[0, 1]])
    self.assertTrue(1118 in aBits)
    self.assertEqual(aBits[1118], [[0, 1, 2]])
    self.assertTrue(562 in aBits)
    self.assertEqual(aBits[562], [[2]])
    self.assertTrue(1772 in aBits)
    self.assertEqual(aBits[1772], [[0], [1]])

  def testSimpleAromaticity(self):
    m = Chem.MolFromSmiles('c1ccccc1')
    self.assertTrue(m.GetBondWithIdx(0).GetIsAromatic())
    self.assertTrue(m.GetAtomWithIdx(0).GetIsAromatic())
    Chem.Kekulize(m, True)
    self.assertFalse(m.GetBondWithIdx(0).GetIsAromatic())
    self.assertFalse(m.GetAtomWithIdx(0).GetIsAromatic())
    Chem.SetAromaticity(m, Chem.AROMATICITY_SIMPLE)
    self.assertTrue(m.GetBondWithIdx(0).GetIsAromatic())
    self.assertTrue(m.GetAtomWithIdx(0).GetIsAromatic())

    m = Chem.MolFromSmiles('c1c[nH]cc1')
    self.assertTrue(m.GetBondWithIdx(0).GetIsAromatic())
    self.assertTrue(m.GetAtomWithIdx(0).GetIsAromatic())
    Chem.Kekulize(m, True)
    self.assertFalse(m.GetBondWithIdx(0).GetIsAromatic())
    self.assertFalse(m.GetAtomWithIdx(0).GetIsAromatic())
    Chem.SetAromaticity(m, Chem.AROMATICITY_SIMPLE)
    self.assertTrue(m.GetBondWithIdx(0).GetIsAromatic())
    self.assertTrue(m.GetAtomWithIdx(0).GetIsAromatic())

    m = Chem.MolFromSmiles('c1cccoocc1')
    self.assertTrue(m.GetBondWithIdx(0).GetIsAromatic())
    self.assertTrue(m.GetAtomWithIdx(0).GetIsAromatic())
    Chem.Kekulize(m, True)
    self.assertFalse(m.GetBondWithIdx(0).GetIsAromatic())
    self.assertFalse(m.GetAtomWithIdx(0).GetIsAromatic())
    Chem.SetAromaticity(m, Chem.AROMATICITY_SIMPLE)
    self.assertFalse(m.GetBondWithIdx(0).GetIsAromatic())
    self.assertFalse(m.GetAtomWithIdx(0).GetIsAromatic())

    m = Chem.MolFromSmiles('c1ooc1')
    self.assertTrue(m.GetBondWithIdx(0).GetIsAromatic())
    self.assertTrue(m.GetAtomWithIdx(0).GetIsAromatic())
    Chem.Kekulize(m, True)
    self.assertFalse(m.GetBondWithIdx(0).GetIsAromatic())
    self.assertFalse(m.GetAtomWithIdx(0).GetIsAromatic())
    Chem.SetAromaticity(m, Chem.AROMATICITY_SIMPLE)
    self.assertFalse(m.GetBondWithIdx(0).GetIsAromatic())
    self.assertFalse(m.GetAtomWithIdx(0).GetIsAromatic())

    m = Chem.MolFromSmiles('C1=CC2=CC=CC=CC2=C1')
    self.assertTrue(m.GetBondWithIdx(0).GetIsAromatic())
    self.assertTrue(m.GetAtomWithIdx(0).GetIsAromatic())
    Chem.Kekulize(m, True)
    self.assertFalse(m.GetBondWithIdx(0).GetIsAromatic())
    self.assertFalse(m.GetAtomWithIdx(0).GetIsAromatic())
    Chem.SetAromaticity(m, Chem.AROMATICITY_SIMPLE)
    self.assertFalse(m.GetBondWithIdx(0).GetIsAromatic())
    self.assertFalse(m.GetAtomWithIdx(0).GetIsAromatic())

  def testGithub955(self):
    m = Chem.MolFromSmiles("CCC")
    m.GetAtomWithIdx(0).SetProp("foo", "1")
    self.assertEqual(list(m.GetAtomWithIdx(0).GetPropNames()), ["foo"])
    m.GetBondWithIdx(0).SetProp("foo", "1")
    self.assertEqual(list(m.GetBondWithIdx(0).GetPropNames()), ["foo"])

  def testMDLProps(self):
    m = Chem.MolFromSmiles("CCC")
    m.GetAtomWithIdx(0).SetAtomMapNum(1)
    Chem.SetAtomAlias(m.GetAtomWithIdx(1), "foo")
    Chem.SetAtomValue(m.GetAtomWithIdx(1), "bar")

    m = Chem.MolFromMolBlock(Chem.MolToMolBlock(m))
    self.assertEqual(m.GetAtomWithIdx(0).GetAtomMapNum(), 1)
    self.assertEqual(Chem.GetAtomAlias(m.GetAtomWithIdx(1)), "foo")
    self.assertEqual(Chem.GetAtomValue(m.GetAtomWithIdx(1)), "bar")

  def testSmilesProps(self):
    m = Chem.MolFromSmiles("C")
    Chem.SetSupplementalSmilesLabel(m.GetAtomWithIdx(0), 'xxx')
    self.assertEqual(Chem.MolToSmiles(m), "Cxxx")

  def testGithub1051(self):
    # just need to test that this exists:
    self.assertTrue(Chem.BondDir.EITHERDOUBLE)

  def testGithub1041(self):
    a = Chem.Atom(6)
    self.assertRaises(RuntimeError, lambda: a.GetOwningMol())
    self.assertRaises(RuntimeError, lambda: a.GetNeighbors())
    self.assertRaises(RuntimeError, lambda: a.GetBonds())
    self.assertRaises(RuntimeError, lambda: a.IsInRing())
    self.assertRaises(RuntimeError, lambda: a.IsInRingSize(4))

  def testSmilesParseParams(self):
    smi = "CCC |$foo;;bar$| ourname"
    m = Chem.MolFromSmiles(smi)
    self.assertTrue(m is not None)
    ps = Chem.SmilesParserParams()
    ps.allowCXSMILES = False
    ps.parseName = False
    m = Chem.MolFromSmiles(smi, ps)
    self.assertTrue(m is None)
    ps.allowCXSMILES = True
    ps.parseName = True
    m = Chem.MolFromSmiles(smi, ps)
    self.assertTrue(m is not None)
    self.assertTrue(m.GetAtomWithIdx(0).HasProp('atomLabel'))
    self.assertEqual(m.GetAtomWithIdx(0).GetProp('atomLabel'), "foo")
    self.assertTrue(m.HasProp('_Name'))
    self.assertEqual(m.GetProp('_Name'), "ourname")
    self.assertEqual(m.GetProp("_CXSMILES_Data"), "|$foo;;bar$|")

  def testWriteCXSmiles(self):
    smi = "CCC |$foo;;bar$|"
    ps = Chem.SmilesParserParams()
    ps.allowCXSMILES = True
    m = Chem.MolFromSmiles(smi, ps)
    self.assertTrue(m is not None)
    self.assertTrue(m.GetAtomWithIdx(0).HasProp('atomLabel'))
    self.assertEqual(m.GetAtomWithIdx(0).GetProp('atomLabel'), "foo")
    self.assertEqual(Chem.MolToCXSmiles(m), 'CCC |$foo;;bar$|')

    smi = "Cl.CCC |$;foo;;bar$|"
    m = Chem.MolFromSmiles(smi, ps)
    self.assertTrue(m is not None)
    self.assertTrue(m.GetAtomWithIdx(1).HasProp('atomLabel'))
    self.assertEqual(m.GetAtomWithIdx(1).GetProp('atomLabel'), "foo")
    self.assertEqual(Chem.MolFragmentToCXSmiles(m, atomsToUse=(1, 2, 3)), 'CCC |$foo;;bar$|')

  def testPickleProps(self):
    import pickle
    m = Chem.MolFromSmiles('C1=CN=CC=C1')
    m.SetProp("_Name", "Name")
    for atom in m.GetAtoms():
      atom.SetProp("_foo", "bar" + str(atom.GetIdx()))
      atom.SetProp("foo", "baz" + str(atom.GetIdx()))

    Chem.SetDefaultPickleProperties(Chem.PropertyPickleOptions.AllProps)
    pkl = pickle.dumps(m)
    m2 = pickle.loads(pkl)
    smi1 = Chem.MolToSmiles(m)
    smi2 = Chem.MolToSmiles(m2)
    self.assertTrue(smi1 == smi2)
    self.assertEqual(m2.GetProp("_Name"), "Name")
    for atom in m2.GetAtoms():
      self.assertEqual(atom.GetProp("_foo"), "bar" + str(atom.GetIdx()))
      self.assertEqual(atom.GetProp("foo"), "baz" + str(atom.GetIdx()))

    Chem.SetDefaultPickleProperties(Chem.PropertyPickleOptions.AtomProps)
    pkl = pickle.dumps(m)
    m2 = pickle.loads(pkl)
    smi1 = Chem.MolToSmiles(m)
    smi2 = Chem.MolToSmiles(m2)
    self.assertTrue(smi1 == smi2)
    self.assertFalse(m2.HasProp("_Name"))
    for atom in m2.GetAtoms():
      self.assertFalse(atom.HasProp("_foo"))
      self.assertEqual(atom.GetProp("foo"), "baz" + str(atom.GetIdx()))

    Chem.SetDefaultPickleProperties(Chem.PropertyPickleOptions.NoProps)
    pkl = pickle.dumps(m)
    m2 = pickle.loads(pkl)
    smi1 = Chem.MolToSmiles(m)
    smi2 = Chem.MolToSmiles(m2)
    self.assertTrue(smi1 == smi2)
    self.assertFalse(m2.HasProp("_Name"))
    for atom in m2.GetAtoms():
      self.assertFalse(atom.HasProp("_foo"))
      self.assertFalse(atom.HasProp("foo"))

    Chem.SetDefaultPickleProperties(Chem.PropertyPickleOptions.MolProps
                                    | Chem.PropertyPickleOptions.PrivateProps)
    pkl = pickle.dumps(m)
    m2 = pickle.loads(pkl)
    smi1 = Chem.MolToSmiles(m)
    smi2 = Chem.MolToSmiles(m2)
    self.assertTrue(smi1 == smi2)
    self.assertEqual(m2.GetProp("_Name"), "Name")
    for atom in m2.GetAtoms():
      self.assertFalse(atom.HasProp("_foo"))
      self.assertFalse(atom.HasProp("foo"))

  def testGithub1352(self):
    self.assertTrue('SP' in Chem.HybridizationType.names)
    self.assertTrue('S' in Chem.HybridizationType.names)
    m = Chem.MolFromSmiles('CC(=O)O.[Na]')
    self.assertEqual(m.GetAtomWithIdx(0).GetHybridization().name, 'SP3')
    self.assertEqual(m.GetAtomWithIdx(4).GetHybridization().name, 'S')

  def testGithub1366(self):
    mol = Chem.MolFromSmiles('*C*')
    mol = Chem.RWMol(mol)
    ats = iter(mol.GetAtoms())
    atom = next(ats)
    mol.RemoveAtom(atom.GetIdx())
    self.assertRaises(RuntimeError, next, ats)

    mol = Chem.MolFromSmiles('*C*')
    mol = Chem.RWMol(mol)
    bonds = iter(mol.GetBonds())
    bond = next(bonds)
    mol.RemoveBond(bond.GetBeginAtomIdx(), bond.GetEndAtomIdx())
    self.assertRaises(RuntimeError, next, bonds)

  def testGithub1478(self):
    data = """
  MJ150720

  8  8  0  0  0  0  0  0  0  0999 V2000
   -0.4242   -1.4883    0.0000 O   0  0  0  0  0  0  0  0  0  0  0  0
    0.2901   -1.0758    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
    1.0046    0.9865    0.0000 A   0  0  0  0  0  0  0  0  0  0  0  0
    1.0046    0.1614    0.0000 A   0  0  0  0  0  0  0  0  0  0  0  0
    0.2901   -0.2508    0.0000 A   0  0  0  0  0  0  0  0  0  0  0  0
   -0.4243    0.1614    0.0000 A   0  0  0  0  0  0  0  0  0  0  0  0
   -0.4243    0.9865    0.0000 A   0  0  0  0  0  0  0  0  0  0  0  0
    0.2901    1.3990    0.0000 A   0  0  0  0  0  0  0  0  0  0  0  0
  7  6  4  0  0  0  0
  8  7  4  0  0  0  0
  6  5  4  0  0  0  0
  5  4  4  0  0  0  0
  5  2  1  0  0  0  0
  4  3  4  0  0  0  0
  8  3  4  0  0  0  0
  2  1  2  0  0  0  0
M  END
"""
    pattern = Chem.MolFromMolBlock(data)
    m = Chem.MolFromSmiles("c1ccccc1C=O")
    self.assertTrue(m.HasSubstructMatch(pattern))

  def testGithub1320(self):
    import pickle
    mol = Chem.MolFromSmiles('N[C@@H](C)O')
    mol2 = pickle.loads(pickle.dumps(mol))
    self.assertEqual(Chem.MolToSmiles(mol, isomericSmiles=True),
                     Chem.MolToSmiles(mol2, isomericSmiles=True))
    Chem.SetDefaultPickleProperties(Chem.PropertyPickleOptions.AtomProps
                                    | Chem.PropertyPickleOptions.BondProps
                                    | Chem.PropertyPickleOptions.MolProps
                                    | Chem.PropertyPickleOptions.PrivateProps
                                    | Chem.PropertyPickleOptions.ComputedProps)
    mol3 = pickle.loads(pickle.dumps(mol))

    for a1, a2 in zip(mol.GetAtoms(), mol3.GetAtoms()):
      d1 = a1.GetPropsAsDict()
      d2 = a2.GetPropsAsDict()
      if "__computedProps" in d1:
        c1 = list(d1["__computedProps"])
        c2 = list(d2["__computedProps"])
        del d1["__computedProps"]
        del d2["__computedProps"]
        self.assertEqual(c1, c2)

      assert d1 == d2

    for a1, a2 in zip(mol.GetBonds(), mol3.GetBonds()):
      d1 = a1.GetPropsAsDict()
      d2 = a2.GetPropsAsDict()
      if "__computedProps" in d1:
        c1 = list(d1["__computedProps"])
        c2 = list(d2["__computedProps"])
        del d1["__computedProps"]
        del d2["__computedProps"]
        self.assertEqual(c1, c2)

      assert d1 == d2

    self.assertEqual(Chem.MolToSmiles(mol, isomericSmiles=True),
                     Chem.MolToSmiles(mol3, isomericSmiles=True))

  def testOldPropPickles(self):
    data = 'crdkit.Chem.rdchem\nMol\np0\n(S\'\\xef\\xbe\\xad\\xde\\x00\\x00\\x00\\x00\\x08\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00)\\x00\\x00\\x00-\\x00\\x00\\x00\\x80\\x01\\x06\\x00`\\x00\\x00\\x00\\x01\\x03\\x07\\x00`\\x00\\x00\\x00\\x02\\x01\\x06 4\\x00\\x00\\x00\\x01\\x01\\x04\\x06\\x00`\\x00\\x00\\x00\\x01\\x03\\x06\\x00(\\x00\\x00\\x00\\x03\\x04\\x08\\x00(\\x00\\x00\\x00\\x03\\x02\\x07\\x00h\\x00\\x00\\x00\\x03\\x02\\x01\\x06 4\\x00\\x00\\x00\\x02\\x01\\x04\\x06\\x00(\\x00\\x00\\x00\\x03\\x04\\x08\\x00(\\x00\\x00\\x00\\x03\\x02\\x07\\x00(\\x00\\x00\\x00\\x03\\x03\\x06\\x00`\\x00\\x00\\x00\\x02\\x02\\x06 4\\x00\\x00\\x00\\x01\\x01\\x04\\x08\\x00(\\x00\\x00\\x00\\x03\\x02\\x06@(\\x00\\x00\\x00\\x03\\x04\\x06@h\\x00\\x00\\x00\\x03\\x03\\x01\\x06@h\\x00\\x00\\x00\\x03\\x03\\x01\\x06@h\\x00\\x00\\x00\\x03\\x03\\x01\\x06@h\\x00\\x00\\x00\\x03\\x03\\x01\\x06@h\\x00\\x00\\x00\\x03\\x03\\x01\\x06\\x00`\\x00\\x00\\x00\\x02\\x02\\x06 4\\x00\\x00\\x00\\x01\\x01\\x04\\x06\\x00(\\x00\\x00\\x00\\x03\\x04\\x08\\x00(\\x00\\x00\\x00\\x03\\x02\\x07\\x00h\\x00\\x00\\x00\\x03\\x02\\x01\\x06 4\\x00\\x00\\x00\\x02\\x01\\x04\\x06\\x00`\\x00\\x00\\x00\\x02\\x02\\x06\\x00`\\x00\\x00\\x00\\x02\\x02\\x06\\x00`\\x00\\x00\\x00\\x02\\x02\\x06@(\\x00\\x00\\x00\\x03\\x04\\x06@h\\x00\\x00\\x00\\x03\\x03\\x01\\x06@h\\x00\\x00\\x00\\x03\\x03\\x01\\x06@h\\x00\\x00\\x00\\x03\\x03\\x01\\x06@h\\x00\\x00\\x00\\x03\\x03\\x01\\x06@(\\x00\\x00\\x00\\x03\\x04\\x06\\x00`\\x00\\x00\\x00\\x03\\x01\\x06\\x00`\\x00\\x00\\x00\\x02\\x02\\x06\\x00`\\x00\\x00\\x00\\x02\\x02\\x06\\x00`\\x00\\x00\\x00\\x02\\x02\\x06\\x00`\\x00\\x00\\x00\\x02\\x02\\x06\\x00`\\x00\\x00\\x00\\x02\\x02\\x0b\\x00\\x01\\x00\\x01\\x02\\x00\\x02\\x03\\x00\\x02\\x04\\x00\\x04\\x05(\\x02\\x04\\x06 \\x06\\x07\\x00\\x07\\x08\\x00\\x08\\t(\\x02\\x08\\n \\n\\x0b\\x00\\x0b\\x0c\\x00\\x0c\\r\\x00\\r\\x0e \\x0e\\x0fh\\x0c\\x0f\\x10h\\x0c\\x10\\x11h\\x0c\\x11\\x12h\\x0c\\x12\\x13h\\x0c\\x0c\\x14\\x00\\x14\\x15\\x00\\x15\\x16\\x00\\x16\\x17(\\x02\\x16\\x18 \\x18\\x19\\x00\\x19\\x1a\\x00\\x1a\\x1b\\x00\\x1b\\x1c\\x00\\x1c\\x1d\\x00\\x1d\\x1eh\\x0c\\x1e\\x1fh\\x0c\\x1f h\\x0c !h\\x0c!"h\\x0c\\x07#\\x00#$\\x00$%\\x00%&\\x00&\\\'\\x00\\\'(\\x00\\x15\\n\\x00"\\x19\\x00(#\\x00\\x13\\x0eh\\x0c"\\x1dh\\x0c\\x14\\x05\\x05\\x0b\\n\\x15\\x14\\x0c\\x06\\x0f\\x10\\x11\\x12\\x13\\x0e\\x06\\x1a\\x1b\\x1c\\x1d"\\x19\\x06\\x1e\\x1f !"\\x1d\\x06$%&\\\'(#\\x17\\x00\\x00\\x00\\x00\\x12\\x03\\x00\\x00\\x00\\x07\\x00\\x00\\x00numArom\\x01\\x02\\x00\\x00\\x00\\x0f\\x00\\x00\\x00_StereochemDone\\x01\\x01\\x00\\x00\\x00\\x03\\x00\\x00\\x00foo\\x00\\x03\\x00\\x00\\x00bar\\x13:\\x02\\x00\\x00\\x00\\x08\\x00\\x00\\x00_CIPRank\\x02\\x12\\x00\\x00\\x00\\x05\\x00\\x00\\x00myidx\\x00\\x01\\x00\\x00\\x000\\x02\\x00\\x00\\x00\\x08\\x00\\x00\\x00_CIPRank\\x02\\x1d\\x00\\x00\\x00\\x05\\x00\\x00\\x00myidx\\x00\\x01\\x00\\x00\\x001\\x04\\x00\\x00\\x00\\x08\\x00\\x00\\x00_CIPRank\\x02\\x15\\x00\\x00\\x00\\x12\\x00\\x00\\x00_ChiralityPossible\\x01\\x01\\x00\\x00\\x00\\x08\\x00\\x00\\x00_CIPCode\\x00\\x01\\x00\\x00\\x00S\\x05\\x00\\x00\\x00myidx\\x00\\x01\\x00\\x00\\x002\\x02\\x00\\x00\\x00\\x08\\x00\\x00\\x00_CIPRank\\x02\\x00\\x00\\x00\\x00\\x05\\x00\\x00\\x00myidx\\x00\\x01\\x00\\x00\\x003\\x02\\x00\\x00\\x00\\x08\\x00\\x00\\x00_CIPRank\\x02\\x1a\\x00\\x00\\x00\\x05\\x00\\x00\\x00myidx\\x00\\x01\\x00\\x00\\x004\\x02\\x00\\x00\\x00\\x08\\x00\\x00\\x00_CIPRank\\x02"\\x00\\x00\\x00\\x05\\x00\\x00\\x00myidx\\x00\\x01\\x00\\x00\\x005\\x02\\x00\\x00\\x00\\x08\\x00\\x00\\x00_CIPRank\\x02\\x1f\\x00\\x00\\x00\\x05\\x00\\x00\\x00myidx\\x00\\x01\\x00\\x00\\x006\\x04\\x00\\x00\\x00\\x08\\x00\\x00\\x00_CIPRank\\x02\\x16\\x00\\x00\\x00\\x12\\x00\\x00\\x00_ChiralityPossible\\x01\\x01\\x00\\x00\\x00\\x08\\x00\\x00\\x00_CIPCode\\x00\\x01\\x00\\x00\\x00S\\x05\\x00\\x00\\x00myidx\\x00\\x01\\x00\\x00\\x007\\x02\\x00\\x00\\x00\\x08\\x00\\x00\\x00_CIPRank\\x02\\x1c\\x00\\x00\\x00\\x05\\x00\\x00\\x00myidx\\x00\\x01\\x00\\x00\\x008\\x02\\x00\\x00\\x00\\x08\\x00\\x00\\x00_CIPRank\\x02$\\x00\\x00\\x00\\x05\\x00\\x00\\x00myidx\\x00\\x01\\x00\\x00\\x009\\x02\\x00\\x00\\x00\\x08\\x00\\x00\\x00_CIPRank\\x02 \\x00\\x00\\x00\\x05\\x00\\x00\\x00myidx\\x00\\x02\\x00\\x00\\x0010\\x02\\x00\\x00\\x00\\x08\\x00\\x00\\x00_CIPRank\\x02\\x13\\x00\\x00\\x00\\x05\\x00\\x00\\x00myidx\\x00\\x02\\x00\\x00\\x0011\\x04\\x00\\x00\\x00\\x08\\x00\\x00\\x00_CIPRank\\x02\\x18\\x00\\x00\\x00\\x12\\x00\\x00\\x00_ChiralityPossible\\x01\\x01\\x00\\x00\\x00\\x08\\x00\\x00\\x00_CIPCode\\x00\\x01\\x00\\x00\\x00S\\x05\\x00\\x00\\x00myidx\\x00\\x02\\x00\\x00\\x0012\\x02\\x00\\x00\\x00\\x08\\x00\\x00\\x00_CIPRank\\x02!\\x00\\x00\\x00\\x05\\x00\\x00\\x00myidx\\x00\\x02\\x00\\x00\\x0013\\x02\\x00\\x00\\x00\\x08\\x00\\x00\\x00_CIPRank\\x02\\x19\\x00\\x00\\x00\\x05\\x00\\x00\\x00myidx\\x00\\x02\\x00\\x00\\x0014\\x02\\x00\\x00\\x00\\x08\\x00\\x00\\x00_CIPRank\\x02\\x0f\\x00\\x00\\x00\\x05\\x00\\x00\\x00myidx\\x00\\x02\\x00\\x00\\x0015\\x02\\x00\\x00\\x00\\x08\\x00\\x00\\x00_CIPRank\\x02\\x0b\\x00\\x00\\x00\\x05\\x00\\x00\\x00myidx\\x00\\x02\\x00\\x00\\x0016\\x02\\x00\\x00\\x00\\x08\\x00\\x00\\x00_CIPRank\\x02\\x08\\x00\\x00\\x00\\x05\\x00\\x00\\x00myidx\\x00\\x02\\x00\\x00\\x0017\\x02\\x00\\x00\\x00\\x08\\x00\\x00\\x00_CIPRank\\x02\\x0b\\x00\\x00\\x00\\x05\\x00\\x00\\x00myidx\\x00\\x02\\x00\\x00\\x0018\\x02\\x00\\x00\\x00\\x08\\x00\\x00\\x00_CIPRank\\x02\\x0f\\x00\\x00\\x00\\x05\\x00\\x00\\x00myidx\\x00\\x02\\x00\\x00\\x0019\\x02\\x00\\x00\\x00\\x08\\x00\\x00\\x00_CIPRank\\x02\\x07\\x00\\x00\\x00\\x05\\x00\\x00\\x00myidx\\x00\\x02\\x00\\x00\\x0020\\x04\\x00\\x00\\x00\\x08\\x00\\x00\\x00_CIPRank\\x02\\x17\\x00\\x00\\x00\\x12\\x00\\x00\\x00_ChiralityPossible\\x01\\x01\\x00\\x00\\x00\\x08\\x00\\x00\\x00_CIPCode\\x00\\x01\\x00\\x00\\x00S\\x05\\x00\\x00\\x00myidx\\x00\\x02\\x00\\x00\\x0021\\x02\\x00\\x00\\x00\\x08\\x00\\x00\\x00_CIPRank\\x02\\x1b\\x00\\x00\\x00\\x05\\x00\\x00\\x00myidx\\x00\\x02\\x00\\x00\\x0022\\x02\\x00\\x00\\x00\\x08\\x00\\x00\\x00_CIPRank\\x02#\\x00\\x00\\x00\\x05\\x00\\x00\\x00myidx\\x00\\x02\\x00\\x00\\x0023\\x02\\x00\\x00\\x00\\x08\\x00\\x00\\x00_CIPRank\\x02\\x1e\\x00\\x00\\x00\\x05\\x00\\x00\\x00myidx\\x00\\x02\\x00\\x00\\x0024\\x04\\x00\\x00\\x00\\x08\\x00\\x00\\x00_CIPRank\\x02\\x14\\x00\\x00\\x00\\x12\\x00\\x00\\x00_ChiralityPossible\\x01\\x01\\x00\\x00\\x00\\x08\\x00\\x00\\x00_CIPCode\\x00\\x01\\x00\\x00\\x00R\\x05\\x00\\x00\\x00myidx\\x00\\x02\\x00\\x00\\x0025\\x02\\x00\\x00\\x00\\x08\\x00\\x00\\x00_CIPRank\\x02\\x06\\x00\\x00\\x00\\x05\\x00\\x00\\x00myidx\\x00\\x02\\x00\\x00\\x0026\\x02\\x00\\x00\\x00\\x08\\x00\\x00\\x00_CIPRank\\x02\\x03\\x00\\x00\\x00\\x05\\x00\\x00\\x00myidx\\x00\\x02\\x00\\x00\\x0027\\x02\\x00\\x00\\x00\\x08\\x00\\x00\\x00_CIPRank\\x02\\x05\\x00\\x00\\x00\\x05\\x00\\x00\\x00myidx\\x00\\x02\\x00\\x00\\x0028\\x02\\x00\\x00\\x00\\x08\\x00\\x00\\x00_CIPRank\\x02\\x10\\x00\\x00\\x00\\x05\\x00\\x00\\x00myidx\\x00\\x02\\x00\\x00\\x0029\\x02\\x00\\x00\\x00\\x08\\x00\\x00\\x00_CIPRank\\x02\\x0c\\x00\\x00\\x00\\x05\\x00\\x00\\x00myidx\\x00\\x02\\x00\\x00\\x0030\\x02\\x00\\x00\\x00\\x08\\x00\\x00\\x00_CIPRank\\x02\\t\\x00\\x00\\x00\\x05\\x00\\x00\\x00myidx\\x00\\x02\\x00\\x00\\x0031\\x02\\x00\\x00\\x00\\x08\\x00\\x00\\x00_CIPRank\\x02\\n\\x00\\x00\\x00\\x05\\x00\\x00\\x00myidx\\x00\\x02\\x00\\x00\\x0032\\x02\\x00\\x00\\x00\\x08\\x00\\x00\\x00_CIPRank\\x02\\r\\x00\\x00\\x00\\x05\\x00\\x00\\x00myidx\\x00\\x02\\x00\\x00\\x0033\\x02\\x00\\x00\\x00\\x08\\x00\\x00\\x00_CIPRank\\x02\\x11\\x00\\x00\\x00\\x05\\x00\\x00\\x00myidx\\x00\\x02\\x00\\x00\\x0034\\x02\\x00\\x00\\x00\\x08\\x00\\x00\\x00_CIPRank\\x02\\x0e\\x00\\x00\\x00\\x05\\x00\\x00\\x00myidx\\x00\\x02\\x00\\x00\\x0035\\x02\\x00\\x00\\x00\\x08\\x00\\x00\\x00_CIPRank\\x02\\x04\\x00\\x00\\x00\\x05\\x00\\x00\\x00myidx\\x00\\x02\\x00\\x00\\x0036\\x02\\x00\\x00\\x00\\x08\\x00\\x00\\x00_CIPRank\\x02\\x02\\x00\\x00\\x00\\x05\\x00\\x00\\x00myidx\\x00\\x02\\x00\\x00\\x0037\\x02\\x00\\x00\\x00\\x08\\x00\\x00\\x00_CIPRank\\x02\\x01\\x00\\x00\\x00\\x05\\x00\\x00\\x00myidx\\x00\\x02\\x00\\x00\\x0038\\x02\\x00\\x00\\x00\\x08\\x00\\x00\\x00_CIPRank\\x02\\x02\\x00\\x00\\x00\\x05\\x00\\x00\\x00myidx\\x00\\x02\\x00\\x00\\x0039\\x02\\x00\\x00\\x00\\x08\\x00\\x00\\x00_CIPRank\\x02\\x04\\x00\\x00\\x00\\x05\\x00\\x00\\x00myidx\\x00\\x02\\x00\\x00\\x0040\\x13\\x16\'\np1\ntp2\nRp3\n.'
    import pickle

    # bonds were broken in v1
    m2 = pickle.loads(data.encode("utf-8"), encoding='bytes')

    self.assertEqual(m2.GetProp("foo"), "bar")
    for atom in m2.GetAtoms():
      self.assertEqual(atom.GetProp("myidx"), str(atom.GetIdx()))

    self.assertEqual(
      Chem.MolToSmiles(m2, True),
      Chem.MolToSmiles(
        Chem.MolFromSmiles(
          "CN[C@@H](C)C(=O)N[C@H](C(=O)N1C[C@@H](Oc2ccccc2)C[C@H]1C(=O)N[C@@H]1CCCc2ccccc21)C1CCCCC1"
        ), True))

  def testGithub1461(self):
    # this is simple, it should throw a precondition and not seg fault
    m = Chem.RWMol()
    try:
      m.AddBond(0, 1, Chem.BondType.SINGLE)
      self.assertFalse(True)  # shouldn't get here
    except RuntimeError:
      pass

  def testMolBundles1(self):
    b = Chem.MolBundle()
    smis = ('CC(Cl)(F)CC(F)(Br)', 'C[C@](Cl)(F)C[C@H](F)(Br)', 'C[C@](Cl)(F)C[C@@H](F)(Br)')
    for smi in smis:
      b.AddMol(Chem.MolFromSmiles(smi))
    self.assertEqual(len(b), 3)
    self.assertEqual(b.Size(), 3)
    self.assertRaises(IndexError, lambda: b[4])
    self.assertEqual(Chem.MolToSmiles(b[1], isomericSmiles=True),
                     Chem.MolToSmiles(Chem.MolFromSmiles(smis[1]), isomericSmiles=True))
    self.assertTrue(b.HasSubstructMatch(Chem.MolFromSmiles('CC(Cl)(F)CC(F)(Br)'),
                                        useChirality=True))
    self.assertTrue(
      b.HasSubstructMatch(Chem.MolFromSmiles('C[C@](Cl)(F)C[C@@H](F)(Br)'), useChirality=True))
    self.assertTrue(
      b.HasSubstructMatch(Chem.MolFromSmiles('C[C@@](Cl)(F)C[C@@H](F)(Br)'), useChirality=False))
    self.assertFalse(
      b.HasSubstructMatch(Chem.MolFromSmiles('C[C@@](Cl)(F)C[C@@H](F)(Br)'), useChirality=True))

    self.assertEqual(
      len(b.GetSubstructMatch(Chem.MolFromSmiles('CC(Cl)(F)CC(F)(Br)'), useChirality=True)), 8)
    self.assertEqual(
      len(b.GetSubstructMatch(Chem.MolFromSmiles('C[C@](Cl)(F)C[C@@H](F)(Br)'), useChirality=True)),
      8)
    self.assertEqual(
      len(b.GetSubstructMatch(Chem.MolFromSmiles('C[C@@](Cl)(F)C[C@@H](F)(Br)'),
                              useChirality=False)), 8)
    self.assertEqual(
      len(b.GetSubstructMatch(Chem.MolFromSmiles('C[C@@](Cl)(F)C[C@@H](F)(Br)'),
                              useChirality=True)), 0)

    self.assertEqual(
      len(b.GetSubstructMatches(Chem.MolFromSmiles('CC(Cl)(F)CC(F)(Br)'), useChirality=True)), 1)
    self.assertEqual(
      len(b.GetSubstructMatches(Chem.MolFromSmiles('C[C@](Cl)(F)C[C@@H](F)(Br)'),
                                useChirality=True)), 1)
    self.assertEqual(
      len(
        b.GetSubstructMatches(Chem.MolFromSmiles('C[C@@](Cl)(F)C[C@@H](F)(Br)'),
                              useChirality=False)), 1)
    self.assertEqual(
      len(
        b.GetSubstructMatches(Chem.MolFromSmiles('C[C@@](Cl)(F)C[C@@H](F)(Br)'),
                              useChirality=True)), 0)
    self.assertEqual(
      len(b.GetSubstructMatches(Chem.MolFromSmiles('CC(Cl)(F)CC(F)(Br)'), useChirality=True)[0]), 8)
    self.assertEqual(
      len(
        b.GetSubstructMatches(Chem.MolFromSmiles('C[C@](Cl)(F)C[C@@H](F)(Br)'),
                              useChirality=True)[0]), 8)
    self.assertEqual(
      len(
        b.GetSubstructMatches(Chem.MolFromSmiles('C[C@@](Cl)(F)C[C@@H](F)(Br)'),
                              useChirality=False)[0]), 8)

    if Chem.MolBundleCanSerialize():
      for b2 in (pickle.loads(pickle.dumps(b)), Chem.MolBundle(b.ToBinary())):
        self.assertEqual(len(b2), len(b))
        for m, m2 in zip(b, b2):
          self.assertEqual(Chem.MolToSmiles(m), Chem.MolToSmiles(m2))

  def testMolBundles2(self):
    b = Chem.MolBundle()
    smis = ('Fc1c(Cl)cccc1', 'Fc1cc(Cl)ccc1', 'Fc1ccc(Cl)cc1')
    for smi in smis:
      b.AddMol(Chem.MolFromSmiles(smi))
    self.assertEqual(len(b), 3)
    self.assertEqual(b.Size(), 3)
    self.assertTrue(Chem.MolFromSmiles('Fc1c(Cl)cccc1').HasSubstructMatch(b))
    self.assertTrue(Chem.MolFromSmiles('Fc1cc(Cl)ccc1').HasSubstructMatch(b))
    self.assertTrue(Chem.MolFromSmiles('Fc1c(Cl)cccc1C').HasSubstructMatch(b))
    self.assertTrue(Chem.MolFromSmiles('Fc1cc(Cl)ccc1C').HasSubstructMatch(b))
    self.assertFalse(Chem.MolFromSmiles('Fc1c(Br)cccc1').HasSubstructMatch(b))

    self.assertEqual(len(Chem.MolFromSmiles('Fc1c(Cl)cccc1').GetSubstructMatch(b)), 8)
    self.assertEqual(len(Chem.MolFromSmiles('Fc1c(Cl)cccc1').GetSubstructMatches(b)), 1)
    self.assertEqual(len(Chem.MolFromSmiles('Fc1c(Cl)cccc1').GetSubstructMatches(b)[0]), 8)
    self.assertEqual(len(Chem.MolFromSmiles('Fc1ccc(Cl)cc1').GetSubstructMatches(b)), 1)
    self.assertEqual(
      len(Chem.MolFromSmiles('Fc1ccc(Cl)cc1').GetSubstructMatches(b, uniquify=False)), 2)

    self.assertEqual(len(Chem.MolFromSmiles('Fc1c(C)cccc1').GetSubstructMatch(b)), 0)
    self.assertEqual(len(Chem.MolFromSmiles('Fc1c(C)cccc1').GetSubstructMatches(b)), 0)

  def testMolBundles3(self):
    smis = ('CCC', 'CCO', 'CCN')
    b = Chem.FixedMolSizeMolBundle()
    for smi in smis:
      b.AddMol(Chem.MolFromSmiles(smi))
    self.assertEqual(len(b), 3)
    self.assertEqual(b.Size(), 3)
    with self.assertRaises(ValueError):
      b.AddMol(Chem.MolFromSmiles('CCCC'))

    b = Chem.MolBundle()
    for smi in smis:
      b.AddMol(Chem.MolFromSmiles(smi))
    self.assertEqual(len(b), 3)
    self.assertEqual(b.Size(), 3)
    b.AddMol(Chem.MolFromSmiles('CCCC'))
    self.assertEqual(b.Size(), 4)

  def testGithub1622(self):
    nonaromatics = (
      "C1=C[N]C=C1",  # radicals are not two electron donors
      "O=C1C=CNC=C1",  # exocyclic double bonds don't steal electrons
      "C1=CS(=O)C=C1",  # not sure how to classify this example from the
      # OEChem docs
      "C1#CC=CC=C1"  # benzyne
      # 5-membered heterocycles
      "C1=COC=C1",  # furan
      "C1=CSC=C1",  # thiophene
      "C1=CNC=C1",  #pyrrole
      "C1=COC=N1",  # oxazole
      "C1=CSC=N1",  # thiazole
      "C1=CNC=N1",  # imidazole
      "C1=CNN=C1",  # pyrazole
      "C1=CON=C1",  # isoxazole
      "C1=CSN=C1",  # isothiazole
      "C1=CON=N1",  # 1,2,3-oxadiazole
      "C1=CNN=N1",  # 1,2,3-triazole
      "N1=CSC=N1",  # 1,3,4-thiadiazole
      # not outside the second rows
      "C1=CC=C[Si]=C1",
      "C1=CC=CC=P1",
      # 5-membered heterocycles outside the second row
      "C1=C[Se]C=C1",
      'C1=C[Te]C=C1')
    for smi in nonaromatics:
      m = Chem.MolFromSmiles(smi, sanitize=False)
      Chem.SanitizeMol(m, Chem.SANITIZE_ALL ^ Chem.SANITIZE_SETAROMATICITY)
      Chem.SetAromaticity(m, Chem.AROMATICITY_MDL)
      self.assertFalse(m.GetAtomWithIdx(0).GetIsAromatic())
    aromatics = (
      "C1=CC=CC=C1",  # benzene, of course
      # hetrocyclics
      "N1=CC=CC=C1",  # pyridine
      "N1=CC=CC=N1",  # pyridazine
      "N1=CC=CN=C1",  # pyrimidine
      "N1=CC=NC=C1",  # pyrazine
      "N1=CN=CN=C1",  # 1,3,5-triazine
      # polycyclic aromatics
      "C1=CC2=CC=CC=CC2=C1",  # azulene
      "C1=CC=CC2=CC=CC=C12",
      "C1=CC2=CC=CC=CC=C12",
      "C1=CC=C2C(=C1)N=CC=N2",
      "C1=CN=CC2C=CC=CC1=2",
      "C1=CC=C2C(=C1)N=C3C=CC=CC3=N2",
      "C1=CN=NC2C=CC=CC1=2",
      # macrocycle aromatics
      "C1=CC=CC=CC=CC=C1",
      "C1=CC=CC=CC=CC=CC=CC=CC=CC=C1",
      "N1=CN=NC=CC=CC=CC=CC=CC=CC=CC=CC=CC=CC=CC=CC=C1")
    for smi in aromatics:
      m = Chem.MolFromSmiles(smi, sanitize=False)
      Chem.SanitizeMol(m, Chem.SANITIZE_ALL ^ Chem.SANITIZE_SETAROMATICITY)
      Chem.SetAromaticity(m, Chem.AROMATICITY_MDL)
      self.assertTrue(m.GetAtomWithIdx(0).GetIsAromatic())

  def testMolBlockChirality(self):
    m = Chem.MolFromSmiles('C[C@H](Cl)Br')
    mb = Chem.MolToMolBlock(m)
    m2 = Chem.MolFromMolBlock(mb)
    csmi1 = Chem.MolToSmiles(m, isomericSmiles=True)
    csmi2 = Chem.MolToSmiles(m2, isomericSmiles=True)
    self.assertEqual(csmi1, csmi2)

  def testIssue1735(self):
    # this shouldn't seg fault...
    m = Chem.RWMol()
    ranks = Chem.CanonicalRankAtoms(m, breakTies=False)
    ranks = Chem.CanonicalRankAtoms(m, breakTies=True)

  def testGithub1615(self):
    mb = """Issue399a.mol
  ChemDraw04050615582D

  4  4  0  0  0  0  0  0  0  0999 V2000
   -0.7697    0.0000    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
    0.0553    0.0000    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
    0.7697    0.4125    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
    0.7697   -0.4125    0.0000 O   0  0  0  0  0  0  0  0  0  0  0  0
  2  1  1  0
  2  3  1  0
  3  4  1  0
  2  4  1  0
M  END"""
    m = Chem.MolFromMolBlock(mb)
    self.assertFalse(m.GetAtomWithIdx(1).HasProp("_CIPCode"))
    self.assertEqual(m.GetBondWithIdx(0).GetBondDir(), Chem.BondDir.NONE)
    self.assertEqual(m.GetAtomWithIdx(1).GetChiralTag(), Chem.ChiralType.CHI_UNSPECIFIED)
    m.GetAtomWithIdx(1).SetChiralTag(Chem.ChiralType.CHI_TETRAHEDRAL_CW)
    Chem.AssignStereochemistry(m, force=True)
    self.assertTrue(m.GetAtomWithIdx(1).HasProp("_CIPCode"))
    self.assertEqual(m.GetAtomWithIdx(1).GetProp("_CIPCode"), "S")
    self.assertEqual(m.GetBondWithIdx(0).GetBondDir(), Chem.BondDir.NONE)
    Chem.WedgeBond(m.GetBondWithIdx(0), 1, m.GetConformer())
    self.assertEqual(m.GetBondWithIdx(0).GetBondDir(), Chem.BondDir.BEGINWEDGE)

  def testSmilesToAtom(self):
    a = Chem.AtomFromSmiles("C")
    self.assertEqual(a.GetAtomicNum(), 6)
    b = Chem.BondFromSmiles("=")
    self.assertEqual(b.GetBondType(), Chem.BondType.DOUBLE)
    a = Chem.AtomFromSmiles("error")
    self.assertIs(a, None)
    b = Chem.BondFromSmiles("d")
    self.assertIs(b, None)

    a = Chem.AtomFromSmarts("C")
    self.assertEqual(a.GetAtomicNum(), 6)
    b = Chem.BondFromSmarts("=")
    self.assertEqual(b.GetBondType(), Chem.BondType.DOUBLE)
    a = Chem.AtomFromSmarts("error")
    self.assertIs(a, None)
    b = Chem.BondFromSmarts("d")
    self.assertIs(b, None)

  def testSVGParsing(self):
    svg = """<?xml version='1.0' encoding='iso-8859-1'?>
<svg version='1.1' baseProfile='full'
              xmlns='http://www.w3.org/2000/svg'
                      xmlns:rdkit='http://www.rdkit.org/xml'
                      xmlns:xlink='http://www.w3.org/1999/xlink'
                  xml:space='preserve'
width='200px' height='200px' >
<rect style='opacity:1.0;fill:#FFFFFF;stroke:none' width='200' height='200' x='0' y='0'> </rect>
<path d='M 9.09091,89.4974 24.2916,84.7462' style='fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:2px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1' />
<path d='M 24.2916,84.7462 39.4923,79.9949' style='fill:none;fill-rule:evenodd;stroke:#0000FF;stroke-width:2px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1' />
<path d='M 86.2908,106.814 75.1709,93.4683 72.0765,96.8285 86.2908,106.814' style='fill:#000000;fill-rule:evenodd;stroke:#000000;stroke-width:2px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1' />
<path d='M 75.1709,93.4683 57.8622,86.8431 64.051,80.1229 75.1709,93.4683' style='fill:#0000FF;fill-rule:evenodd;stroke:#0000FF;stroke-width:2px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1' />
<path d='M 75.1709,93.4683 72.0765,96.8285 57.8622,86.8431 75.1709,93.4683' style='fill:#0000FF;fill-rule:evenodd;stroke:#0000FF;stroke-width:2px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1' />
<path d='M 86.2908,106.814 82.1459,125.293' style='fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:2px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1' />
<path d='M 82.1459,125.293 78.0009,143.772' style='fill:none;fill-rule:evenodd;stroke:#00CC00;stroke-width:2px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1' />
<path d='M 86.2908,106.814 129.89,93.1862' style='fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:2px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1' />
<path d='M 134.347,94.186 138.492,75.7069' style='fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:2px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1' />
<path d='M 138.492,75.7069 142.637,57.2277' style='fill:none;fill-rule:evenodd;stroke:#FF0000;stroke-width:2px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1' />
<path d='M 125.432,92.1865 129.577,73.7074' style='fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:2px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1' />
<path d='M 129.577,73.7074 133.722,55.2282' style='fill:none;fill-rule:evenodd;stroke:#FF0000;stroke-width:2px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1' />
<path d='M 129.89,93.1862 142.557,104.852' style='fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:2px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1' />
<path d='M 142.557,104.852 155.224,116.517' style='fill:none;fill-rule:evenodd;stroke:#FF0000;stroke-width:2px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1' />
<text x='39.4923' y='83.483' style='font-size:15px;font-style:normal;font-weight:normal;fill-opacity:1;stroke:none;font-family:sans-serif;text-anchor:start;fill:#0000FF' ><tspan>NH</tspan></text>
<text x='67.6656' y='158.998' style='font-size:15px;font-style:normal;font-weight:normal;fill-opacity:1;stroke:none;font-family:sans-serif;text-anchor:start;fill:#00CC00' ><tspan>Cl</tspan></text>
<text x='132.777' y='56.228' style='font-size:15px;font-style:normal;font-weight:normal;fill-opacity:1;stroke:none;font-family:sans-serif;text-anchor:start;fill:#FF0000' ><tspan>O</tspan></text>
<text x='149.782' y='131.743' style='font-size:15px;font-style:normal;font-weight:normal;fill-opacity:1;stroke:none;font-family:sans-serif;text-anchor:start;fill:#FF0000' ><tspan>OH</tspan></text>
<text x='89.9952' y='194' style='font-size:12px;font-style:normal;font-weight:normal;fill-opacity:1;stroke:none;font-family:sans-serif;text-anchor:start;fill:#000000' ><tspan>m1</tspan></text>
<metadata>
<rdkit:mol xmlns:rdkit = "http://www.rdkit.org/xml" version="0.9">
<rdkit:atom idx="1" atom-smiles="[CH3]" drawing-x="9.09091" drawing-y="89.4974" x="-2.78651" y="0.295614" z="0" />
<rdkit:atom idx="2" atom-smiles="[NH]" drawing-x="52.6897" drawing-y="75.8699" x="-1.35482" y="0.743114" z="0" />
<rdkit:atom idx="3" atom-smiles="[C@H]" drawing-x="86.2908" drawing-y="106.814" x="-0.251428" y="-0.273019" z="0" />
<rdkit:atom idx="4" atom-smiles="[Cl]" drawing-x="76.2932" drawing-y="151.385" x="-0.579728" y="-1.73665" z="0" />
<rdkit:atom idx="5" atom-smiles="[C]" drawing-x="129.89" drawing-y="93.1862" x="1.18027" y="0.174481" z="0" />
<rdkit:atom idx="6" atom-smiles="[O]" drawing-x="139.887" drawing-y="48.6148" x="1.50857" y="1.63811" z="0" />
<rdkit:atom idx="7" atom-smiles="[OH]" drawing-x="163.491" drawing-y="124.13" x="2.28366" y="-0.841652" z="0" />
<rdkit:bond idx="1" begin-atom-idx="1" end-atom-idx="2" bond-smiles="-" />
<rdkit:bond idx="2" begin-atom-idx="2" end-atom-idx="3" bond-smiles="-" />
<rdkit:bond idx="3" begin-atom-idx="3" end-atom-idx="4" bond-smiles="-" />
<rdkit:bond idx="4" begin-atom-idx="3" end-atom-idx="5" bond-smiles="-" />
<rdkit:bond idx="5" begin-atom-idx="5" end-atom-idx="6" bond-smiles="=" />
<rdkit:bond idx="6" begin-atom-idx="5" end-atom-idx="7" bond-smiles="-" />
</rdkit:mol></metadata>
</svg>"""
    mol = Chem.MolFromRDKitSVG(svg)
    self.assertEqual(mol.GetNumAtoms(), 7)
    self.assertEqual(Chem.MolToSmiles(mol), 'CN[C@H](Cl)C(=O)O')

    svg2 = """<?xml version='1.0' encoding='iso-8859-1'?>
<svg version='1.1' baseProfile='full'
          xmlns='http://www.w3.org/2000/svg'
                  xmlns:rdkit='http://www.rdkit.org/xml'
                  xmlns:xlink='http://www.w3.org/1999/xlink'
              xml:space='preserve'
width='200px' height='200px' >
<rect style='opacity:1.0;fill:#FFFFFF;stroke:none' width='200' height='200' x='0' y='0'> </rect>
<path d='M 9.09091,89.4974 24.2916,84.7462' style='fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:2px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1' />
<path d='M 24.2916,84.7462 39.4923,79.9949' style='fill:none;fill-rule:evenodd;stroke:#0000FF;stroke-width:2px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1' />
<path d='M 86.2908,106.814 75.1709,93.4683 72.0765,96.8285 86.2908,106.814' style='fill:#000000;fill-rule:evenodd;stroke:#000000;stroke-width:2px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1' />
<path d='M 75.1709,93.4683 57.8622,86.8431 64.051,80.1229 75.1709,93.4683' style='fill:#0000FF;fill-rule:evenodd;stroke:#0000FF;stroke-width:2px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1' />
<path d='M 75.1709,93.4683 72.0765,96.8285 57.8622,86.8431 75.1709,93.4683' style='fill:#0000FF;fill-rule:evenodd;stroke:#0000FF;stroke-width:2px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1' />
<path d='M 86.2908,106.814 82.1459,125.293' style='fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:2px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1' />
<path d='M 82.1459,125.293 78.0009,143.772' style='fill:none;fill-rule:evenodd;stroke:#00CC00;stroke-width:2px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1' />
<path d='M 86.2908,106.814 129.89,93.1862' style='fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:2px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1' />
<path d='M 134.347,94.186 138.492,75.7069' style='fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:2px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1' />
<path d='M 138.492,75.7069 142.637,57.2277' style='fill:none;fill-rule:evenodd;stroke:#FF0000;stroke-width:2px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1' />
<path d='M 125.432,92.1865 129.577,73.7074' style='fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:2px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1' />
<path d='M 129.577,73.7074 133.722,55.2282' style='fill:none;fill-rule:evenodd;stroke:#FF0000;stroke-width:2px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1' />
<path d='M 129.89,93.1862 142.557,104.852' style='fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:2px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1' />
<path d='M 142.557,104.852 155.224,116.517' style='fill:none;fill-rule:evenodd;stroke:#FF0000;stroke-width:2px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1' />
<text x='39.4923' y='83.483' style='font-size:15px;font-style:normal;font-weight:normal;fill-opacity:1;stroke:none;font-family:sans-serif;text-anchor:start;fill:#0000FF' ><tspan>NH</tspan></text>
<text x='67.6656' y='158.998' style='font-size:15px;font-style:normal;font-weight:normal;fill-opacity:1;stroke:none;font-family:sans-serif;text-anchor:start;fill:#00CC00' ><tspan>Cl</tspan></text>
<text x='132.777' y='56.228' style='font-size:15px;font-style:normal;font-weight:normal;fill-opacity:1;stroke:none;font-family:sans-serif;text-anchor:start;fill:#FF0000' ><tspan>O</tspan></text>
<text x='149.782' y='131.743' style='font-size:15px;font-style:normal;font-weight:normal;fill-opacity:1;stroke:none;font-family:sans-serif;text-anchor:start;fill:#FF0000' ><tspan>OH</tspan></text>
<text x='89.9952' y='194' style='font-size:12px;font-style:normal;font-weight:normal;fill-opacity:1;stroke:none;font-family:sans-serif;text-anchor:start;fill:#000000' ><tspan>m1</tspan></text>
</svg>"""
    mol = Chem.MolFromRDKitSVG(svg2)
    self.assertTrue(mol is None)

    with self.assertRaises(RuntimeError):
      mol = Chem.MolFromRDKitSVG("bad svg")

  def testAssignChiralTypesFromBondDirs(self):
    """
    Just check to see that AssignChiralTypesFromBondDirs is wrapped.
    Critical tests of the underlying C++ function already exist
    in SD file reader tests.
    """
    mol = Chem.MolFromSmiles('C(F)(Cl)Br')
    rdkit.Chem.rdDepictor.Compute2DCoords(mol)
    atom0 = mol.GetAtomWithIdx(0)
    self.assertEqual(atom0.GetChiralTag(), Chem.rdchem.ChiralType.CHI_UNSPECIFIED)
    bond = mol.GetBondBetweenAtoms(0, 1)
    bond.SetBondDir(Chem.rdchem.BondDir.BEGINWEDGE)
    Chem.AssignChiralTypesFromBondDirs(mol)
    self.assertEqual(atom0.GetChiralTag(), Chem.rdchem.ChiralType.CHI_TETRAHEDRAL_CCW)

  def testAssignStereochemistryFrom3D(self):

    def _stereoTester(mol, expectedCIP, expectedStereo):
      mol.UpdatePropertyCache()
      self.assertEqual(mol.GetNumAtoms(), 9)
      self.assertFalse(mol.GetAtomWithIdx(1).HasProp("_CIPCode"))
      self.assertEqual(mol.GetBondWithIdx(3).GetStereo(), Chem.BondStereo.STEREONONE)
      for bond in mol.GetBonds():
        bond.SetBondDir(Chem.BondDir.NONE)

      Chem.AssignStereochemistryFrom3D(mol)
      self.assertTrue(mol.GetAtomWithIdx(1).HasProp("_CIPCode"))
      self.assertEqual(mol.GetAtomWithIdx(1).GetProp("_CIPCode"), expectedCIP)
      self.assertEqual(mol.GetBondWithIdx(3).GetStereo(), expectedStereo)

    fileN = os.path.join(RDConfig.RDBaseDir, 'Code', 'GraphMol', 'test_data', 'stereochem.sdf')
    suppl = Chem.SDMolSupplier(fileN, sanitize=False)
    expected = (
      ("R", Chem.BondStereo.STEREOZ),
      ("R", Chem.BondStereo.STEREOE),
      ("S", Chem.BondStereo.STEREOZ),
      ("S", Chem.BondStereo.STEREOE),
    )
    for i, mol in enumerate(suppl):
      cip, stereo = expected[i]
      _stereoTester(mol, cip, stereo)

  def testGitHub2082(self):
    ctab = """
  MJ150720

  9  9  0  0  0  0  0  0  0  0999 V2000
    2.5687   -0.7144    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
    2.1562    0.0000    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
    2.5687    0.7144    0.0000 O   0  0  0  0  0  0  0  0  0  0  0  0
    1.3312    0.0000    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
    0.9187   -0.7144    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
    0.0937   -0.7144    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
   -0.3187    0.0000    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
    0.0937    0.7144    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
    0.9187    0.7144    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
  2  1  1  6
  2  3  1  0
  2  4  1  0
  4  5  2  0
  5  6  1  0
  6  7  2  0
  7  8  1  0
  8  9  2  0
  9  4  1  0
M  END
"""
    mol = Chem.MolFromMolBlock(ctab)
    self.assertFalse(mol.GetConformer().Is3D())
    self.assertTrue("@" in Chem.MolToSmiles(mol, True))

  def testGitHub2082_2(self):
    # test a mol block that lies is 3D but labelled 2D
    ofile = os.path.join(RDConfig.RDBaseDir, 'Code', 'GraphMol', 'Wrap', 'test_data',
                         'issue2082.mol')
    with open(ofile) as inf:
      ctab = inf.read()
    m = Chem.MolFromMolBlock(ctab)
    self.assertTrue(m.GetConformer().Is3D())

  def testSetQuery(self):
    from rdkit.Chem import rdqueries
    pat = Chem.MolFromSmarts("[C]")
    self.assertFalse(Chem.MolFromSmiles("c1ccccc1").HasSubstructMatch(pat))

    q = rdqueries.AtomNumEqualsQueryAtom(6)
    for atom in pat.GetAtoms():
      atom.SetQuery(q)

    self.assertTrue(Chem.MolFromSmiles("c1ccccc1").HasSubstructMatch(pat))

  def testGetQueryType(self):
    query_a = os.path.join(RDConfig.RDBaseDir, 'Code', 'GraphMol', 'FileParsers', 'test_data',
                           'query_A.mol')
    m = next(Chem.SDMolSupplier(query_a))
    self.assertTrue(m.GetAtomWithIdx(6).HasQuery())
    self.assertTrue(m.GetAtomWithIdx(6).GetQueryType() == "A")

    query_a_v3k = os.path.join(RDConfig.RDBaseDir, 'Code', 'GraphMol', 'FileParsers', 'test_data',
                               'query_A.v3k.mol')
    m = next(Chem.SDMolSupplier(query_a_v3k))
    self.assertTrue(m.GetAtomWithIdx(6).HasQuery())
    self.assertTrue(m.GetAtomWithIdx(6).GetQueryType() == "A")

    query_q = os.path.join(RDConfig.RDBaseDir, 'Code', 'GraphMol', 'FileParsers', 'test_data',
                           'query_Q.mol')
    m = next(Chem.SDMolSupplier(query_q))
    self.assertTrue(m.GetAtomWithIdx(6).HasQuery())
    self.assertTrue(m.GetAtomWithIdx(6).GetQueryType() == "Q")

    query_q_v3k = os.path.join(RDConfig.RDBaseDir, 'Code', 'GraphMol', 'FileParsers', 'test_data',
                               'query_Q.v3k.mol')
    m = next(Chem.SDMolSupplier(query_q_v3k))
    self.assertTrue(m.GetAtomWithIdx(6).HasQuery())
    self.assertTrue(m.GetAtomWithIdx(6).GetQueryType() == "Q")

    m = Chem.MolFromSmiles("*CC")
    params = Chem.rdmolops.AdjustQueryParameters.NoAdjustments()
    params.makeDummiesQueries = True
    m = Chem.rdmolops.AdjustQueryProperties(m, params)
    self.assertTrue(m.GetAtomWithIdx(0).HasQuery())
    self.assertTrue(m.GetAtomWithIdx(0).GetQueryType() == "")

  def testBondSetQuery(self):
    pat = Chem.MolFromSmarts('[#6]=[#6]')
    mol = Chem.MolFromSmiles("c1ccccc1")
    self.assertFalse(mol.HasSubstructMatch(pat))
    pat2 = Chem.MolFromSmarts('C:C')
    for bond in pat.GetBonds():
      bond.SetQuery(pat2.GetBondWithIdx(0))
    self.assertTrue(mol.HasSubstructMatch(pat))

  def testBondExpandQuery(self):
    pat = Chem.MolFromSmarts('C-C')
    mol = Chem.MolFromSmiles("C=C-C")
    self.assertEqual(len(mol.GetSubstructMatches(pat)), 1)
    pat2 = Chem.MolFromSmarts('C=C')
    for bond in pat.GetBonds():
      bond.ExpandQuery(pat2.GetBondWithIdx(0), Chem.CompositeQueryType.COMPOSITE_OR)
    self.assertEqual(len(mol.GetSubstructMatches(pat)), 2)

  def testGitHub1985(self):
    # simple check, this used to throw an exception
    try:
      Chem.MolToSmarts(Chem.MolFromSmarts("[C@]"))
    except Exception:
      self.fail("[C@] caused an exception when roundtripping smarts")

  def testGetEnhancedStereo(self):

    rdbase = os.environ['RDBASE']
    filename = os.path.join(rdbase, 'Code/GraphMol/FileParsers/test_data/two_centers_or.mol')
    m = Chem.MolFromMolFile(filename)

    sg = m.GetStereoGroups()
    self.assertEqual(len(sg), 2)
    group1 = sg[1]
    self.assertEqual(group1.GetGroupType(), Chem.StereoGroupType.STEREO_OR)
    stereo_atoms = group1.GetAtoms()
    self.assertEqual(len(stereo_atoms), 2)
    # file is 1 indexed and says 5
    self.assertEqual(stereo_atoms[1].GetIdx(), 4)

    # make sure the atoms are connected to the parent molecule
    stereo_atoms[1].SetProp("foo", "bar")
    self.assertTrue(m.GetAtomWithIdx(4).HasProp("foo"))

    # make sure that we can iterate over the atoms:
    for at in stereo_atoms:
      at.SetProp("foo2", "bar2")
      self.assertTrue(m.GetAtomWithIdx(at.GetIdx()).HasProp("foo2"))

  def testEnhancedStereoPreservesMol(self):
    """
    Check that the stereo group (and the atoms therein) preserve the lifetime
    of the associated mol.
    """
    rdbase = os.environ['RDBASE']
    filename = os.path.join(rdbase, 'Code/GraphMol/FileParsers/test_data/two_centers_or.mol')
    m = Chem.MolFromMolFile(filename)

    sg = m.GetStereoGroups()
    m = None
    gc.collect()
    self.assertEqual(len(sg), 2)
    group1 = sg[1]
    stereo_atoms = group1.GetAtoms()
    sg = None
    gc.collect()
    self.assertEqual(stereo_atoms[1].GetIdx(), 4)
    self.assertEqual(stereo_atoms[1].GetOwningMol().GetNumAtoms(), 8)

  def testSetEnhancedStereoGroup(self):
    m = Chem.MolFromSmiles('F[C@@H](Br)[C@H](F)Cl |o1:1|')
    m2 = Chem.RWMol(m)

    groups = m2.GetStereoGroups()
    self.assertEqual(len(groups), 1)
    # Can clear the StereoGroups by setting to an empty list
    m2.SetStereoGroups([])
    self.assertEqual(len(m2.GetStereoGroups()), 0)

    # Can add new StereoGroups
    group1 = Chem.rdchem.CreateStereoGroup(Chem.rdchem.StereoGroupType.STEREO_OR, m2, [1])
    m2.SetStereoGroups([group1])
    self.assertEqual(len(m2.GetStereoGroups()), 1)

  def testSetEnhancedStereoGroupOwnershipCheck(self):
    # make sure that the object returned by CreateStereoGroup()
    # preserves the owning molecule:
    m = Chem.RWMol(Chem.MolFromSmiles('F[C@@H](Br)[C@H](F)Cl'))
    group1 = Chem.rdchem.CreateStereoGroup(Chem.rdchem.StereoGroupType.STEREO_OR, m, [1])
    m.SetStereoGroups([group1])
    self.assertEqual(len(m.GetStereoGroups()), 1)

    m = None
    gc.collect()
    stereo_atoms = group1.GetAtoms()
    self.assertEqual(stereo_atoms[0].GetIdx(), 1)
    return
    self.assertEqual(stereo_atoms[0].GetOwningMol().GetNumAtoms(), 6)

    # make sure we can't add StereoGroups constructed from one molecule
    # to a different one:
    m2 = Chem.RWMol(Chem.MolFromSmiles('F[C@@H](Br)[C@H](F)Cl'))
    with self.assertRaises(ValueError):
      m2.SetStereoGroups([group1])

  def testSetEnhancedStereoTypeChecking(self):
    m = Chem.RWMol(Chem.MolFromSmiles('F[C@@H](Br)[C@H](F)Cl'))

    # List or tuple should be allowed:
    group = Chem.rdchem.CreateStereoGroup(Chem.rdchem.StereoGroupType.STEREO_OR, m, [1, 3])
    group = Chem.rdchem.CreateStereoGroup(Chem.rdchem.StereoGroupType.STEREO_OR, m, (1, 3))

    # Python ValueError (range error) with index past the end
    with self.assertRaises(ValueError):
      group = Chem.rdchem.CreateStereoGroup(Chem.rdchem.StereoGroupType.STEREO_OR, m, [100])

    # Mol is None
    with self.assertRaises(TypeError):
      group = Chem.rdchem.CreateStereoGroup(Chem.rdchem.StereoGroupType.STEREO_OR, None, [1])

    # Atom indices must be numbers
    with self.assertRaises(TypeError):
      group = Chem.rdchem.CreateStereoGroup(Chem.rdchem.StereoGroupType.STEREO_OR, m, [1, 'text'])

  def testSubstructParameters(self):
    m = Chem.MolFromSmiles('C[C@](F)(Cl)OCC')
    p1 = Chem.MolFromSmiles('C[C@](F)(Cl)O')
    p2 = Chem.MolFromSmiles('C[C@@](F)(Cl)O')
    p3 = Chem.MolFromSmiles('CC(F)(Cl)O')

    ps = Chem.SubstructMatchParameters()
    self.assertTrue(m.HasSubstructMatch(p1, ps))
    self.assertTrue(m.HasSubstructMatch(p2, ps))
    self.assertTrue(m.HasSubstructMatch(p3, ps))
    self.assertEqual(m.GetSubstructMatch(p1, ps), (0, 1, 2, 3, 4))
    self.assertEqual(m.GetSubstructMatch(p2, ps), (0, 1, 2, 3, 4))
    self.assertEqual(m.GetSubstructMatch(p3, ps), (0, 1, 2, 3, 4))
    self.assertEqual(m.GetSubstructMatches(p1, ps), ((0, 1, 2, 3, 4), ))
    self.assertEqual(m.GetSubstructMatches(p2, ps), ((0, 1, 2, 3, 4), ))
    self.assertEqual(m.GetSubstructMatches(p3, ps), ((0, 1, 2, 3, 4), ))
    ps.useChirality = True
    self.assertTrue(m.HasSubstructMatch(p1, ps))
    self.assertFalse(m.HasSubstructMatch(p2, ps))
    self.assertTrue(m.HasSubstructMatch(p3, ps))
    self.assertEqual(m.GetSubstructMatch(p1, ps), (0, 1, 2, 3, 4))
    self.assertEqual(m.GetSubstructMatch(p2, ps), ())
    self.assertEqual(m.GetSubstructMatch(p3, ps), (0, 1, 2, 3, 4))
    self.assertEqual(m.GetSubstructMatches(p1, ps), ((0, 1, 2, 3, 4), ))
    self.assertEqual(m.GetSubstructMatches(p2, ps), ())
    self.assertEqual(m.GetSubstructMatches(p3, ps), ((0, 1, 2, 3, 4), ))

  def testForwardEnhancedStereoGroupIds(self):
    m = Chem.MolFromSmiles('C[C@H](O)Cl |o5:1|')
    self.assertIsNotNone(m)

    # StereoGroup id is read, but not forwarded to "write id"
    stgs = m.GetStereoGroups()
    self.assertEqual(len(stgs), 1)
    self.assertEqual(stgs[0].GetGroupType(), Chem.StereoGroupType.STEREO_OR)
    self.assertEqual(stgs[0].GetReadId(), 5)
    self.assertEqual(stgs[0].GetWriteId(), 0)

    self.assertEqual(Chem.MolToCXSmiles(m), 'C[C@H](O)Cl |o1:1|')

    stgs[0].SetWriteId(7)
    self.assertEqual(stgs[0].GetWriteId(), 7)
    self.assertEqual(Chem.MolToCXSmiles(m), 'C[C@H](O)Cl |o7:1|')

    # ids are forwarded to copies of the mol
    m2 = Chem.RWMol(m)
    self.assertIsNotNone(m2)

    stgs2 = m2.GetStereoGroups()
    self.assertEqual(len(stgs), 1)
    self.assertEqual(stgs2[0].GetGroupType(), Chem.StereoGroupType.STEREO_OR)
    self.assertEqual(stgs2[0].GetReadId(), 5)
    self.assertEqual(stgs2[0].GetWriteId(), 7)

    self.assertEqual(Chem.MolToCXSmiles(m2), 'C[C@H](O)Cl |o7:1|')

    # Forwardings the ids overrides the WriteId
    Chem.ForwardStereoGroupIds(m)
    self.assertEqual(stgs[0].GetWriteId(), 5)
    self.assertEqual(Chem.MolToCXSmiles(m), 'C[C@H](O)Cl |o5:1|')

    # the copy mol is not affected
    self.assertEqual(stgs2[0].GetWriteId(), 7)

  def testSubstructParametersBundles(self):
    b = Chem.MolBundle()
    smis = ('C[C@](F)(Cl)O', 'C[C@](Br)(Cl)O', 'C[C@](I)(Cl)O')
    for smi in smis:
      b.AddMol(Chem.MolFromSmiles(smi))
    self.assertEqual(len(b), 3)
    self.assertEqual(b.Size(), 3)
    ps = Chem.SubstructMatchParameters()
    ps.useChirality = True
    self.assertTrue(Chem.MolFromSmiles('C[C@](F)(Cl)OCC').HasSubstructMatch(b, ps))
    self.assertFalse(Chem.MolFromSmiles('C[C@@](F)(Cl)OCC').HasSubstructMatch(b, ps))
    self.assertTrue(Chem.MolFromSmiles('C[C@](I)(Cl)OCC').HasSubstructMatch(b, ps))
    self.assertFalse(Chem.MolFromSmiles('C[C@@](I)(Cl)OCC').HasSubstructMatch(b, ps))

    self.assertEqual(
      Chem.MolFromSmiles('C[C@](F)(Cl)OCC').GetSubstructMatch(b, ps), (0, 1, 2, 3, 4))
    self.assertEqual(Chem.MolFromSmiles('C[C@@](F)(Cl)OCC').GetSubstructMatch(b, ps), ())
    self.assertEqual(
      Chem.MolFromSmiles('C[C@](I)(Cl)OCC').GetSubstructMatch(b, ps), (0, 1, 2, 3, 4))
    self.assertEqual(Chem.MolFromSmiles('C[C@@](I)(Cl)OCC').GetSubstructMatch(b, ps), ())

    self.assertEqual(
      Chem.MolFromSmiles('C[C@](F)(Cl)OCC').GetSubstructMatches(b, ps), ((0, 1, 2, 3, 4), ))
    self.assertEqual(Chem.MolFromSmiles('C[C@@](F)(Cl)OCC').GetSubstructMatches(b, ps), ())
    self.assertEqual(
      Chem.MolFromSmiles('C[C@](I)(Cl)OCC').GetSubstructMatches(b, ps), ((0, 1, 2, 3, 4), ))
    self.assertEqual(Chem.MolFromSmiles('C[C@@](I)(Cl)OCC').GetSubstructMatches(b, ps), ())

  def testSubstructParametersBundles2(self):
    b = Chem.MolBundle()
    smis = ('C[C@](F)(Cl)O', 'C[C@](Br)(Cl)O', 'C[C@](I)(Cl)O')
    for smi in smis:
      b.AddMol(Chem.MolFromSmiles(smi))
    self.assertEqual(len(b), 3)
    b2 = Chem.MolBundle()
    smis = ('C[C@@](F)(Cl)O', 'C[C@@](Br)(Cl)O', 'C[C@@](I)(Cl)O')
    for smi in smis:
      b2.AddMol(Chem.MolFromSmiles(smi))
    self.assertEqual(len(b2), 3)
    ps = Chem.SubstructMatchParameters()
    ps.useChirality = True
    self.assertTrue(b.HasSubstructMatch(b, ps))
    self.assertFalse(b.HasSubstructMatch(b2, ps))
    self.assertFalse(b2.HasSubstructMatch(b, ps))

    self.assertEqual(b.GetSubstructMatch(b, ps), (0, 1, 2, 3, 4))
    self.assertEqual(b.GetSubstructMatch(b2, ps), ())
    self.assertEqual(b2.GetSubstructMatch(b, ps), ())

    self.assertEqual(b.GetSubstructMatches(b, ps), ((0, 1, 2, 3, 4), ))
    self.assertEqual(b.GetSubstructMatches(b2, ps), ())
    self.assertEqual(b2.GetSubstructMatches(b, ps), ())

  def testSubstructMatchAtomProperties(self):
    m = Chem.MolFromSmiles("CCCCCCCCC")
    query = Chem.MolFromSmiles("CCC")
    m.GetAtomWithIdx(0).SetProp("test_prop", "1")
    query.GetAtomWithIdx(0).SetProp("test_prop", "1")
    ps = Chem.SubstructMatchParameters()
    ps.atomProperties = ["test_prop"]

    self.assertEqual(len(m.GetSubstructMatches(query)), 7)
    self.assertEqual(len(m.GetSubstructMatches(query, ps)), 1)

    # more than one property works as well
    m.GetAtomWithIdx(1).SetProp("test_prop2", "1")
    query.GetAtomWithIdx(1).SetProp("test_prop2", "1")
    ps.atomProperties = ["test_prop", "test_prop2"]
    self.assertEqual(len(m.GetSubstructMatches(query, ps)), 1)

  def testSubstructMatchBondProperties(self):
    m = Chem.MolFromSmiles("CCCCCCCCC")
    query = Chem.MolFromSmiles("CCC")
    m.GetBondWithIdx(0).SetProp("test_prop", "1")
    query.GetBondWithIdx(0).SetProp("test_prop", "1")
    ps = Chem.SubstructMatchParameters()
    ps.bondProperties = ["test_prop"]

    self.assertEqual(len(m.GetSubstructMatches(query)), 7)
    self.assertEqual(len(m.GetSubstructMatches(query, ps)), 1)

    # more than one property works as well
    m.GetBondWithIdx(1).SetProp("test_prop2", "1")
    query.GetBondWithIdx(1).SetProp("test_prop2", "1")
    ps.bondProperties = ["test_prop", "test_prop2"]
    self.assertEqual(len(m.GetSubstructMatches(query, ps)), 1)

    # atom and bond properties work together
    m.GetAtomWithIdx(0).SetProp("test_prop", "1")
    query.GetAtomWithIdx(0).SetProp("test_prop", "1")
    ps.atomProperties = ["test_prop"]
    self.assertEqual(len(m.GetSubstructMatches(query, ps)), 1)

  def testGithub2285(self):
    fileN = os.path.join(RDConfig.RDBaseDir, 'Code', 'GraphMol', 'FileParsers', 'test_data',
                         'github2285.sdf')

    supp = Chem.ForwardSDMolSupplier(fileN, removeHs=False)
    if hasattr(supp, "__next__"):
      self.assertTrue(supp.__next__() is not None)
    else:
      self.assertTrue(supp.next() is not None)

  def testBitVectProp(self):
    bv = DataStructs.ExplicitBitVect(100)
    m = Chem.MolFromSmiles("CC")
    for atom in m.GetAtoms():
      bv.SetBit(atom.GetIdx())
      atom.SetExplicitBitVectProp("prop", bv)

    for atom in m.GetAtoms():
      bv = atom.GetExplicitBitVectProp("prop")
      self.assertTrue(bv.GetBit(atom.GetIdx()))

  def testBitVectQuery(self):
    bv = DataStructs.ExplicitBitVect(4)
    bv.SetBit(0)
    bv.SetBit(2)

    # wow, what a mouthfull..
    qa = rdqueries.HasBitVectPropWithValueQueryAtom("prop", bv, tolerance=0.0)

    m = Chem.MolFromSmiles("CC")
    for atom in m.GetAtoms():
      if atom.GetIdx() == 0:
        atom.SetExplicitBitVectProp("prop", bv)

    l = tuple([x.GetIdx() for x in m.GetAtomsMatchingQuery(qa)])
    self.assertEqual(l, (0, ))

    m = Chem.MolFromSmiles("CC")
    for atom in m.GetAtoms():
      bv = DataStructs.ExplicitBitVect(4)
      bv.SetBit(atom.GetIdx())
      atom.SetExplicitBitVectProp("prop", bv)

    sma = Chem.MolFromSmarts("C")
    for atom in sma.GetAtoms():
      bv = DataStructs.ExplicitBitVect(4)
      bv.SetBit(1)
      qa = rdqueries.HasBitVectPropWithValueQueryAtom("prop", bv, tolerance=0.0)
      atom.ExpandQuery(qa)

    res = m.GetSubstructMatches(sma)
    self.assertEqual(res, ((1, ), ))

    sma = Chem.MolFromSmarts("C")
    for atom in sma.GetAtoms():
      bv = DataStructs.ExplicitBitVect(4)
      bv.SetBit(0)
      qa = rdqueries.HasBitVectPropWithValueQueryAtom("prop", bv, tolerance=0.0)
      atom.ExpandQuery(qa)

    res = m.GetSubstructMatches(sma)
    self.assertEqual(res, ((0, ), ))

    sma = Chem.MolFromSmarts("C")
    for atom in sma.GetAtoms():
      bv = DataStructs.ExplicitBitVect(4)
      bv.SetBit(0)
      qa = rdqueries.HasBitVectPropWithValueQueryAtom("prop", bv, tolerance=1.0)
      atom.ExpandQuery(qa)

    res = m.GetSubstructMatches(sma)
    self.assertEqual(res, ((0, ), (1, )))

  def testGithub2441(self):
    m = Chem.MolFromSmiles("CC")
    conf = Chem.Conformer(2)
    m.AddConformer(conf, assignId=False)
    m.GetConformer().SetIntProp("foo", 1)
    m.GetConformer().SetProp("bar", "foo")
    self.assertTrue(m.GetConformer().HasProp("foo"))
    self.assertFalse(m.GetConformer().HasProp("food"))
    d = m.GetConformer().GetPropsAsDict()
    self.assertTrue('foo' in d)
    self.assertTrue('bar' in d)
    self.assertEqual(d['bar'], 'foo')
    self.assertEqual(m.GetConformer().GetProp("bar"), "foo")
    self.assertEqual(m.GetConformer().GetIntProp("foo"), 1)

  def testGithub2479(self):
    # Chemistry failure in last entry
    smi2 = '''c1ccccc  duff
c1ccccc1 ok
c1ccncc1 pyridine
C(C garbage
C1CC1 ok2
C1C(Cl)C1 ok3
CC(C)(C)(C)C duff2
'''
    suppl2 = Chem.SmilesMolSupplier()
    suppl2.SetData(smi2, titleLine=False, nameColumn=1)
    l = [x for x in suppl2]
    self.assertEqual(len(l), 7)
    self.assertTrue(l[6] is None)

    # SMILES failure in last entry
    smi2 = '''c1ccccc  duff
c1ccccc1 ok
c1ccncc1 pyridine
C(C garbage
C1CC1 ok2
C1C(Cl)C1 ok3
C1C(Cl)CCCC duff2
'''
    suppl2 = Chem.SmilesMolSupplier()
    suppl2.SetData(smi2, titleLine=False, nameColumn=1)
    l = [x for x in suppl2]
    self.assertEqual(len(l), 7)
    self.assertTrue(l[6] is None)

    sdf = b"""
  Mrv1810 06051911332D

  3  2  0  0  0  0            999 V2000
  -13.3985    4.9850    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
  -12.7066    5.4343    0.0000 O   0  0  0  0  0  0  0  0  0  0  0  0
  -12.0654    4.9151    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
  1  2  1  0  0  0  0
  2  3  1  0  0  0  0
M  END
$$$$

  Mrv1810 06051911332D

  3  2  0  0  0  0            999 V2000
  -10.3083    4.8496    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
   -9.6408    5.3345    0.0000 F   0  0  0  0  0  0  0  0  0  0  0  0
   -9.0277    4.7825    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
  1  2  1  0  0  0  0
  2  3  1  0  0  0  0
M  END
$$$$

  Mrv1810 06051911332D

  3  2  0  0  0  0            999 V2000
  -10.3083    4.8496    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
   -9.6"""
    suppl3 = Chem.SDMolSupplier()
    suppl3.SetData(sdf)
    l = [x for x in suppl3]
    self.assertEqual(len(l), 3)
    self.assertTrue(l[1] is None)
    self.assertTrue(l[2] is None)

    sio = BytesIO(sdf)
    suppl3 = Chem.ForwardSDMolSupplier(sio)
    l = [x for x in suppl3]
    self.assertEqual(len(l), 3)
    self.assertTrue(l[1] is None)
    self.assertTrue(l[2] is None)

    sdf = b"""
  Mrv1810 06051911332D

  3  2  0  0  0  0            999 V2000
  -13.3985    4.9850    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
  -12.7066    5.4343    0.0000 O   0  0  0  0  0  0  0  0  0  0  0  0
  -12.0654    4.9151    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
  1  2  1  0  0  0  0
  2  3  1  0  0  0  0
M  END
>  <pval>  (1)
[1,2,]

$$$$

  Mrv1810 06051911332D

  3  2  0  0  0  0            999 V2000
  -10.3083    4.8496    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
   -9.6408    5.3345    0.0000 O   0  0  0  0  0  0  0  0  0  0  0  0
   -9.0277    4.7825    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
  1  2  1  0  0  0  0
  2  3  1  0  0  0  0
M  END
>  <pval>  (1)
[1,2,]
"""
    suppl3 = Chem.SDMolSupplier()
    suppl3.SetData(sdf)
    l = [x for x in suppl3]
    self.assertEqual(len(l), 2)
    self.assertTrue(l[0] is not None)
    self.assertTrue(l[1] is not None)

    sio = BytesIO(sdf)
    suppl3 = Chem.ForwardSDMolSupplier(sio)
    l = [x for x in suppl3]
    self.assertEqual(len(l), 2)
    self.assertTrue(l[0] is not None)
    self.assertTrue(l[1] is not None)

  def testCMLWriter(self):
    self.maxDiff = None  # XXX
    conf = Chem.Conformer(11)

    conf.SetAtomPosition(0, [-0.95330, 0.60416, 1.01609])
    conf.SetAtomPosition(1, [-1.00832, 1.68746, 0.83520])
    conf.SetAtomPosition(2, [-1.96274, 0.16103, 0.94471])
    conf.SetAtomPosition(3, [-0.57701, 0.44737, 2.04167])
    conf.SetAtomPosition(4, [0.00000, 0.00000, 0.00000])
    conf.SetAtomPosition(5, [-0.43038, 0.18596, -1.01377])
    conf.SetAtomPosition(6, [0.22538, -1.36531, 0.19373])
    conf.SetAtomPosition(7, [1.21993, -1.33937, 0.14580])
    conf.SetAtomPosition(8, [1.38490, 0.73003, 0.00000])
    conf.SetAtomPosition(9, [1.38490, 1.96795, 0.00000])
    conf.SetAtomPosition(10, [2.35253, -0.07700, 0.00000])

    emol = Chem.EditableMol(Chem.Mol())
    for z in [6, 1, 1, 1, 6, 1, 8, 1, 6, 8, 8]:
      emol.AddAtom(Chem.Atom(z))

    emol.AddBond(0, 1, Chem.BondType.SINGLE)
    emol.AddBond(0, 2, Chem.BondType.SINGLE)
    emol.AddBond(0, 3, Chem.BondType.SINGLE)
    emol.AddBond(0, 4, Chem.BondType.SINGLE)
    emol.AddBond(4, 5, Chem.BondType.SINGLE)
    emol.AddBond(4, 6, Chem.BondType.SINGLE)
    emol.AddBond(4, 8, Chem.BondType.SINGLE)
    emol.AddBond(6, 7, Chem.BondType.SINGLE)
    emol.AddBond(8, 9, Chem.BondType.DOUBLE)
    emol.AddBond(8, 10, Chem.BondType.SINGLE)

    mol = emol.GetMol()
    mol.SetProp('_Name', 'S-lactic acid')
    mol.AddConformer(conf)

    mol.GetAtomWithIdx(7).SetIsotope(2)
    mol.GetAtomWithIdx(10).SetFormalCharge(-1)

    mol.GetAtomWithIdx(4).SetChiralTag(Chem.ChiralType.CHI_TETRAHEDRAL_CCW)

    cmlblock_expected = """<?xml version="1.0" encoding="utf-8"?>
<cml xmlns="http://www.xml-cml.org/schema" xmlns:convention="http://www.xml-cml.org/convention/" convention="convention:molecular">
  <molecule id="m-1" formalCharge="-1" spinMultiplicity="1">
    <name>S-lactic acid</name>
    <atomArray>
      <atom id="a0" elementType="C" formalCharge="0" hydrogenCount="3" x3="-0.953300" y3="0.604160" z3="1.016090"/>
      <atom id="a1" elementType="H" formalCharge="0" hydrogenCount="0" x3="-1.008320" y3="1.687460" z3="0.835200"/>
      <atom id="a2" elementType="H" formalCharge="0" hydrogenCount="0" x3="-1.962740" y3="0.161030" z3="0.944710"/>
      <atom id="a3" elementType="H" formalCharge="0" hydrogenCount="0" x3="-0.577010" y3="0.447370" z3="2.041670"/>
      <atom id="a4" elementType="C" formalCharge="0" hydrogenCount="1" x3="0.000000" y3="0.000000" z3="0.000000">
        <atomParity atomRefs4="a0 a5 a6 a8">1</atomParity>
      </atom>
      <atom id="a5" elementType="H" formalCharge="0" hydrogenCount="0" x3="-0.430380" y3="0.185960" z3="-1.013770"/>
      <atom id="a6" elementType="O" formalCharge="0" hydrogenCount="1" x3="0.225380" y3="-1.365310" z3="0.193730"/>
      <atom id="a7" elementType="H" formalCharge="0" hydrogenCount="0" isotopeNumber="2" x3="1.219930" y3="-1.339370" z3="0.145800"/>
      <atom id="a8" elementType="C" formalCharge="0" hydrogenCount="0" x3="1.384900" y3="0.730030" z3="0.000000"/>
      <atom id="a9" elementType="O" formalCharge="0" hydrogenCount="0" x3="1.384900" y3="1.967950" z3="0.000000"/>
      <atom id="a10" elementType="O" formalCharge="-1" hydrogenCount="0" x3="2.352530" y3="-0.077000" z3="0.000000"/>
    </atomArray>
    <bondArray>
      <bond atomRefs2="a0 a1" id="b0" order="S"/>
      <bond atomRefs2="a0 a2" id="b1" order="S"/>
      <bond atomRefs2="a0 a3" id="b2" order="S"/>
      <bond atomRefs2="a0 a4" id="b3" order="S"/>
      <bond atomRefs2="a4 a5" id="b4" order="S" bondStereo="H"/>
      <bond atomRefs2="a4 a6" id="b5" order="S"/>
      <bond atomRefs2="a4 a8" id="b6" order="S"/>
      <bond atomRefs2="a6 a7" id="b7" order="S"/>
      <bond atomRefs2="a8 a9" id="b8" order="D"/>
      <bond atomRefs2="a8 a10" id="b9" order="S"/>
    </bondArray>
  </molecule>
</cml>
"""

    self.assertEqual(Chem.MolToCMLBlock(mol), cmlblock_expected)

  def testXYZ(self):
    conf = Chem.Conformer(5)
    conf.SetAtomPosition(0, [0.000, 0.000, 0.000])
    conf.SetAtomPosition(1, [-0.635, -0.635, 0.635])
    conf.SetAtomPosition(2, [-0.635, 0.635, -0.635])
    conf.SetAtomPosition(3, [0.635, -0.635, -0.635])
    conf.SetAtomPosition(4, [0.635, 0.635, 0.635])

    emol = Chem.EditableMol(Chem.Mol())
    for z in [6, 1, 1, 1, 1]:
      emol.AddAtom(Chem.Atom(z))
    mol = emol.GetMol()
    mol.SetProp('_Name', 'methane\nthis part should not be output')
    mol.AddConformer(conf)

    xyzblock_expected = """5
methane
C      0.000000    0.000000    0.000000
H     -0.635000   -0.635000    0.635000
H     -0.635000    0.635000   -0.635000
H      0.635000   -0.635000   -0.635000
H      0.635000    0.635000    0.635000
"""

    self.assertEqual(Chem.MolToXYZBlock(mol), xyzblock_expected)

  def testSanitizationExceptionBasics(self):
    try:
      Chem.SanitizeMol(Chem.MolFromSmiles('CFC', sanitize=False))
    except Chem.AtomValenceException as exc:
      self.assertEqual(exc.cause.GetAtomIdx(), 1)
    else:
      self.assertFalse(True)

    try:
      Chem.SanitizeMol(Chem.MolFromSmiles('c1cc1', sanitize=False))
    except Chem.KekulizeException as exc:
      self.assertEqual(exc.cause.GetAtomIndices(), (0, 1, 2))
    else:
      self.assertFalse(True)

  def testSanitizationExceptionHierarchy(self):
    with self.assertRaises(Chem.AtomValenceException):
      Chem.SanitizeMol(Chem.MolFromSmiles('CFC', sanitize=False))
    with self.assertRaises(Chem.AtomSanitizeException):
      Chem.SanitizeMol(Chem.MolFromSmiles('CFC', sanitize=False))
    with self.assertRaises(Chem.MolSanitizeException):
      Chem.SanitizeMol(Chem.MolFromSmiles('CFC', sanitize=False))
    with self.assertRaises(ValueError):
      Chem.SanitizeMol(Chem.MolFromSmiles('CFC', sanitize=False))

    with self.assertRaises(Chem.KekulizeException):
      Chem.SanitizeMol(Chem.MolFromSmiles('c1cc1', sanitize=False))
    with self.assertRaises(Chem.MolSanitizeException):
      Chem.SanitizeMol(Chem.MolFromSmiles('c1cc1', sanitize=False))
    with self.assertRaises(ValueError):
      Chem.SanitizeMol(Chem.MolFromSmiles('c1cc1', sanitize=False))

  def testNoExceptionSmilesParserParams(self):
    """
    MolFromSmiles should catch exceptions even when SmilesParserParams
    is provided.
    """
    smiles_params = Chem.SmilesParserParams()
    mol = Chem.MolFromSmiles("C1CC", smiles_params)
    self.assertIsNone(mol)

  def testDetectChemistryProblems(self):
    m = Chem.MolFromSmiles('CFCc1cc1FC', sanitize=False)
    ps = Chem.DetectChemistryProblems(m)
    self.assertEqual(len(ps), 3)
    self.assertEqual([x.GetType() for x in ps],
                     ['AtomValenceException', 'AtomValenceException', 'KekulizeException'])
    self.assertEqual(ps[0].GetAtomIdx(), 1)
    self.assertEqual(ps[1].GetAtomIdx(), 6)
    self.assertEqual(ps[2].GetAtomIndices(), (3, 4, 5))

  def testGithub2611(self):
    mol = Chem.MolFromSmiles('ONCS.ONCS')
    for atom in mol.GetAtoms():
      atom.SetIsotope(atom.GetIdx())

    order1 = list(
      Chem.CanonicalRankAtomsInFragment(mol, atomsToUse=range(0, 4), breakTies=False,
                                        includeIsotopes=True))
    order2 = list(
      Chem.CanonicalRankAtomsInFragment(mol, atomsToUse=range(0, 8), breakTies=False,
                                        includeIsotopes=False))
    self.assertNotEqual(order1[:4], order2[4:])
    # ensure that the orders are ignored in the second batch
    self.assertEqual(order2[:4], order2[4:])

    for smi in ['ONCS.ONCS', 'F[C@@H](Br)[C@H](F)Cl']:
      mol = Chem.MolFromSmiles(smi)
      for atom in mol.GetAtoms():
        atom.SetIsotope(atom.GetIdx())

        for iso, chiral in [(True, True), (True, False), (False, True), (False, False)]:
          order1 = list(
            Chem.CanonicalRankAtomsInFragment(mol, atomsToUse=range(0, mol.GetNumAtoms()),
                                              bondsToUse=range(0,
                                                               mol.GetNumBonds()), breakTies=False,
                                              includeIsotopes=iso, includeChirality=chiral))
          order2 = list(
            Chem.CanonicalRankAtomsInFragment(mol, atomsToUse=range(0, mol.GetNumAtoms()),
                                              bondsToUse=range(0,
                                                               mol.GetNumBonds()), breakTies=True,
                                              includeIsotopes=iso, includeChirality=chiral))
          order3 = list(
            Chem.CanonicalRankAtoms(mol, breakTies=False, includeIsotopes=iso,
                                    includeChirality=chiral))
          order4 = list(
            Chem.CanonicalRankAtoms(mol, breakTies=True, includeIsotopes=iso,
                                    includeChirality=chiral))
          self.assertEqual(order1, order3)
          self.assertEqual(order2, order4)

  def testSetBondStereoFromDirections(self):
    m1 = Chem.MolFromMolBlock(
      '''
  Mrv1810 10141909482D

  4  3  0  0  0  0            999 V2000
    3.3412   -2.9968    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
    2.5162   -2.9968    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
    2.1037   -3.7112    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
    3.7537   -2.2823    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
  1  2  2  0  0  0  0
  2  3  1  0  0  0  0
  1  4  1  0  0  0  0
M  END
''', sanitize=False)
    self.assertEqual(m1.GetBondBetweenAtoms(0, 1).GetBondType(), Chem.BondType.DOUBLE)
    self.assertEqual(m1.GetBondBetweenAtoms(0, 1).GetStereo(), Chem.BondStereo.STEREONONE)
    Chem.SetBondStereoFromDirections(m1)
    self.assertEqual(m1.GetBondBetweenAtoms(0, 1).GetStereo(), Chem.BondStereo.STEREOTRANS)

    m2 = Chem.MolFromMolBlock(
      '''
  Mrv1810 10141909542D

  4  3  0  0  0  0            999 V2000
    3.4745   -5.2424    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
    2.6495   -5.2424    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
    2.2370   -5.9569    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
    3.8870   -5.9569    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
  1  2  2  0  0  0  0
  2  3  1  0  0  0  0
  1  4  1  0  0  0  0
M  END
''', sanitize=False)
    self.assertEqual(m2.GetBondBetweenAtoms(0, 1).GetBondType(), Chem.BondType.DOUBLE)
    self.assertEqual(m2.GetBondBetweenAtoms(0, 1).GetStereo(), Chem.BondStereo.STEREONONE)
    Chem.SetBondStereoFromDirections(m2)
    self.assertEqual(m2.GetBondBetweenAtoms(0, 1).GetStereo(), Chem.BondStereo.STEREOCIS)

  def testSetBondDirFromStereo(self):
    m1 = Chem.MolFromSmiles('CC=CC')
    m1.GetBondWithIdx(1).SetStereoAtoms(0, 3)
    m1.GetBondWithIdx(1).SetStereo(Chem.BondStereo.STEREOCIS)
    Chem.SetDoubleBondNeighborDirections(m1)
    self.assertEqual(Chem.MolToSmiles(m1), r"C/C=C\C")
    self.assertEqual(m1.GetBondWithIdx(0).GetBondDir(), Chem.BondDir.ENDUPRIGHT)
    self.assertEqual(m1.GetBondWithIdx(2).GetBondDir(), Chem.BondDir.ENDDOWNRIGHT)

  def testAssignChiralTypesFromMolParity(self):

    class TestAssignChiralTypesFromMolParity:

      class BondDef:

        def __init__(self, bi, ei, t):
          self.beginIdx = bi
          self.endIdx = ei
          self.type = t

      def __init__(self, mol, parent):
        self.parent = parent
        self.parityMap = {
          Chem.ChiralType.CHI_TETRAHEDRAL_CW: 1,
          Chem.ChiralType.CHI_TETRAHEDRAL_CCW: 2,
          Chem.ChiralType.CHI_UNSPECIFIED: 0,
          Chem.ChiralType.CHI_OTHER: 0
        }
        self.d_rwMol = Chem.RWMol(mol)
        self.assignMolParity()
        self.fillBondDefVect()
        Chem.AssignAtomChiralTagsFromMolParity(self.d_rwMol)
        self.d_refSmiles = Chem.MolToSmiles(self.d_rwMol)
        self.heapPermutation()

      def assignMolParity(self):
        Chem.AssignAtomChiralTagsFromStructure(self.d_rwMol)
        for a in self.d_rwMol.GetAtoms():
          parity = self.parityMap[a.GetChiralTag()]
          a.SetIntProp("molParity", parity)
          a.SetChiralTag(Chem.ChiralType.CHI_UNSPECIFIED)

      def fillBondDefVect(self):
        self.d_bondDefVect = [
          self.BondDef(b.GetBeginAtomIdx(), b.GetEndAtomIdx(), b.GetBondType())
          for b in self.d_rwMol.GetBonds()
        ]

      def stripBonds(self):
        for i in reversed(range(self.d_rwMol.GetNumBonds())):
          b = self.d_rwMol.GetBondWithIdx(i)
          self.d_rwMol.RemoveBond(b.GetBeginAtomIdx(), b.GetEndAtomIdx())

      def addBonds(self):
        [
          self.d_rwMol.AddBond(bondDef.beginIdx, bondDef.endIdx, bondDef.type)
          for bondDef in self.d_bondDefVect
        ]

      def checkBondPermutation(self):
        self.stripBonds()
        self.addBonds()
        Chem.SanitizeMol(self.d_rwMol)
        Chem.AssignAtomChiralTagsFromMolParity(self.d_rwMol)
        self.parent.assertEqual(Chem.MolToSmiles(self.d_rwMol), self.d_refSmiles)

      def heapPermutation(self, s=0):
        # if size becomes 1 the permutation is ready to use
        if (s == 0):
          s = len(self.d_bondDefVect)
        if (s == 1):
          self.checkBondPermutation()
          return
        for i in range(s):
          self.heapPermutation(s - 1)
          # if size is odd, swap first and last element
          j = 0 if (s % 2 == 1) else i
          self.d_bondDefVect[j], self.d_bondDefVect[s - 1] = \
            self.d_bondDefVect[s - 1], self.d_bondDefVect[j]

    molb = """
     RDKit          3D

  6  5  0  0  1  0  0  0  0  0999 V2000
   -2.9747    1.7234    0.0753 C   0  0  0  0  0  0  0  0  0  0  0  0
   -1.4586    1.4435    0.1253 C   0  0  0  0  0  0  0  0  0  0  0  0
   -3.5885    2.6215    1.4893 Cl  0  0  0  0  0  0  0  0  0  0  0  0
   -3.7306    0.3885   -0.0148 C   0  0  0  0  0  0  0  0  0  0  0  0
   -0.3395    3.0471    0.1580 Br  0  0  0  0  0  0  0  0  0  0  0  0
   -1.1574    0.7125    1.2684 F   0  0  0  0  0  0  0  0  0  0  0  0
  1  2  1  0
  1  3  1  0
  1  4  1  0
  2  5  1  0
  2  6  1  0
M  END
"""
    m = Chem.RWMol(Chem.MolFromMolBlock(molb, sanitize=True, removeHs=False))
    self.assertIsNotNone(m)
    TestAssignChiralTypesFromMolParity(m, self)

  def testCXSMILESErrors(self):
    smi = "CCC |FAILURE|"
    ps = Chem.SmilesParserParams()
    ps.strictCXSMILES = False
    m = Chem.MolFromSmiles(smi, ps)
    self.assertTrue(m is not None)
    self.assertEqual(m.GetNumAtoms(), 3)

  def testRemoveHsParams(self):
    smips = Chem.SmilesParserParams()
    smips.removeHs = False

    m = Chem.MolFromSmiles('F.[H]', smips)
    ps = Chem.RemoveHsParameters()
    m = Chem.RemoveHs(m, ps)
    self.assertEqual(m.GetNumAtoms(), 2)
    ps.removeDegreeZero = True
    m = Chem.RemoveHs(m, ps)
    self.assertEqual(m.GetNumAtoms(), 1)

    m = Chem.MolFromSmiles('F[H-]F', smips)
    ps = Chem.RemoveHsParameters()
    m = Chem.RemoveHs(m, ps)
    self.assertEqual(m.GetNumAtoms(), 3)
    m = Chem.MolFromSmiles('F[H-]F', smips)
    ps.removeHigherDegrees = True
    m = Chem.RemoveHs(m, ps)
    self.assertEqual(m.GetNumAtoms(), 2)

    m = Chem.MolFromSmiles('[H][H]', smips)
    ps = Chem.RemoveHsParameters()
    m = Chem.RemoveHs(m, ps)
    self.assertEqual(m.GetNumAtoms(), 2)
    m = Chem.MolFromSmiles('[H][H]', smips)
    ps.removeOnlyHNeighbors = True
    m = Chem.RemoveHs(m, ps)
    self.assertEqual(m.GetNumAtoms(), 0)

    m = Chem.MolFromSmiles('F[2H]', smips)
    ps = Chem.RemoveHsParameters()
    m = Chem.RemoveHs(m, ps)
    self.assertEqual(m.GetNumAtoms(), 2)
    m = Chem.MolFromSmiles('F[2H]', smips)
    ps.removeIsotopes = True
    m = Chem.RemoveHs(m, ps)
    self.assertEqual(m.GetNumAtoms(), 1)

    m = Chem.MolFromSmiles('c1c(C([2H])([2H])F)cccc1', smips)
    ps = Chem.RemoveHsParameters()
    m_noh = Chem.RemoveHs(m, ps)
    self.assertEqual(m_noh.GetNumAtoms(), m.GetNumAtoms())
    ps.removeAndTrackIsotopes = True
    m_noh = Chem.RemoveHs(m, ps)
    self.assertEqual(m_noh.GetNumAtoms(), m.GetNumAtoms() - 2)
    self.assertTrue(m_noh.GetAtomWithIdx(2).HasProp("_isotopicHs"))
    self.assertEqual(tuple(m_noh.GetAtomWithIdx(2).GetPropsAsDict().get("_isotopicHs")), (2, 2))
    m_h = Chem.AddHs(m_noh)
    self.assertFalse(m_h.GetAtomWithIdx(2).HasProp("_isotopicHs"))
    self.assertEqual(
      sum([
        1 for nbr in m_h.GetAtomWithIdx(2).GetNeighbors()
        if (nbr.GetAtomicNum() == 1 and nbr.GetIsotope())
      ]), 2)

    m = Chem.MolFromSmiles('*[H]', smips)
    ps = Chem.RemoveHsParameters()
    m = Chem.RemoveHs(m, ps)
    self.assertEqual(m.GetNumAtoms(), 2)
    m = Chem.MolFromSmiles('*[H]', smips)
    ps.removeDummyNeighbors = True
    m = Chem.RemoveHs(m, ps)
    self.assertEqual(m.GetNumAtoms(), 1)

    m = Chem.MolFromSmiles('F/C=N/[H]', smips)
    ps = Chem.RemoveHsParameters()
    m = Chem.RemoveHs(m, ps)
    self.assertEqual(m.GetNumAtoms(), 4)
    m = Chem.MolFromSmiles('F/C=N/[H]', smips)
    ps.removeDefiningBondStereo = True
    m = Chem.RemoveHs(m, ps)
    self.assertEqual(m.GetNumAtoms(), 3)

    m = Chem.MolFromSmiles('FC([H])(O)Cl', smips)
    m.GetBondBetweenAtoms(1, 2).SetBondDir(Chem.BondDir.BEGINWEDGE)
    ps = Chem.RemoveHsParameters()
    m = Chem.RemoveHs(m, ps)
    self.assertEqual(m.GetNumAtoms(), 4)
    m = Chem.MolFromSmiles('FC([H])(O)Cl', smips)
    m.GetBondBetweenAtoms(1, 2).SetBondDir(Chem.BondDir.BEGINWEDGE)
    ps.removeWithWedgedBond = False
    m = Chem.RemoveHs(m, ps)
    self.assertEqual(m.GetNumAtoms(), 5)

    m = Chem.MolFromSmarts('F[#1]')
    ps = Chem.RemoveHsParameters()
    m = Chem.RemoveHs(m, ps)
    self.assertEqual(m.GetNumAtoms(), 2)
    m = Chem.MolFromSmarts('F[#1]')
    ps.removeWithQuery = True
    m = Chem.RemoveHs(m, ps)
    self.assertEqual(m.GetNumAtoms(), 1)

    m = Chem.MolFromSmiles('[C@]12([H])CCC1CO2.[H+].F[H-]F.[H][H].[H]*.F/C=C/[H]')
    m = Chem.RemoveAllHs(m)
    for at in m.GetAtoms():
      self.assertNotEqual(at.GetAtomicNum(), 1)

  def testPickleCoordsAsDouble(self):
    import pickle
    m = Chem.MolFromSmiles('C')
    test_num = 1e50
    conf = Chem.Conformer(1)
    conf.SetAtomPosition(0, (test_num, 0.0, 0.0))
    m.AddConformer(conf)

    opts = Chem.GetDefaultPickleProperties()
    Chem.SetDefaultPickleProperties(Chem.PropertyPickleOptions.NoProps)
    self.assertNotEqual(pickle.loads(pickle.dumps(m)).GetConformer().GetAtomPosition(0).x, test_num)

    try:
      Chem.SetDefaultPickleProperties(Chem.PropertyPickleOptions.CoordsAsDouble)
      self.assertEqual(pickle.loads(pickle.dumps(m)).GetConformer().GetAtomPosition(0).x, test_num)
    finally:
      Chem.SetDefaultPickleProperties(opts)

  def testCustomSubstructMatchCheck(self):

    def accept_none(mol, vect):
      return False

    def accept_all(mol, vect):
      return True

    def accept_large(mol, vect):
      return sum(vect) > 5

    m = Chem.MolFromSmiles('CCOCC')
    p = Chem.MolFromSmiles('CCO')
    ps = Chem.SubstructMatchParameters()
    self.assertEqual(len(m.GetSubstructMatches(p, ps)), 2)

    ps.setExtraFinalCheck(accept_none)
    self.assertEqual(len(m.GetSubstructMatches(p, ps)), 0)
    self.assertEqual(len(m.GetSubstructMatch(p, ps)), 0)
    self.assertFalse(m.HasSubstructMatch(p, ps))

    ps.setExtraFinalCheck(accept_all)
    self.assertEqual(len(m.GetSubstructMatches(p, ps)), 2)
    self.assertEqual(len(m.GetSubstructMatch(p, ps)), 3)
    self.assertTrue(m.HasSubstructMatch(p, ps))

    ps.setExtraFinalCheck(accept_large)
    self.assertEqual(len(m.GetSubstructMatches(p, ps)), 1)
    self.assertEqual(len(m.GetSubstructMatch(p, ps)), 3)
    self.assertTrue(m.HasSubstructMatch(p, ps))

  def testMostSubstitutedCoreMatch(self):
    core = Chem.MolFromSmarts("[*:1]c1cc([*:2])ccc1[*:3]")
    orthoMeta = Chem.MolFromSmiles("c1ccc(-c2ccc(-c3ccccc3)c(-c3ccccc3)c2)cc1")
    ortho = Chem.MolFromSmiles("c1ccc(-c2ccccc2-c2ccccc2)cc1")
    meta = Chem.MolFromSmiles("c1ccc(-c2cccc(-c3ccccc3)c2)cc1")
    biphenyl = Chem.MolFromSmiles("c1ccccc1-c1ccccc1")
    phenyl = Chem.MolFromSmiles("c1ccccc1")

    def numHsMatchingDummies(mol, core, match):
      return sum([
        1 for qi, ai in enumerate(match) if core.GetAtomWithIdx(qi).GetAtomicNum() == 0
        and mol.GetAtomWithIdx(ai).GetAtomicNum() == 1
      ])

    for mol, res in ((orthoMeta, 0), (ortho, 1), (meta, 1), (biphenyl, 2), (phenyl, 3)):
      mol = Chem.AddHs(mol)
      matches = mol.GetSubstructMatches(core)
      bestMatch = Chem.GetMostSubstitutedCoreMatch(mol, core, matches)
      self.assertEqual(numHsMatchingDummies(mol, core, bestMatch), res)
      ctrlCounts = sorted([numHsMatchingDummies(mol, core, match) for match in matches])
      sortedCounts = [
        numHsMatchingDummies(mol, core, match)
        for match in Chem.SortMatchesByDegreeOfCoreSubstitution(mol, core, matches)
      ]
      self.assertEqual(len(ctrlCounts), len(sortedCounts))
      self.assertTrue(all(ctrl == sortedCounts[i] for i, ctrl in enumerate(ctrlCounts)))
    with self.assertRaises(ValueError):
      Chem.GetMostSubstitutedCoreMatch(orthoMeta, core, [])
    with self.assertRaises(ValueError):
      Chem.SortMatchesByDegreeOfCoreSubstitution(orthoMeta, core, [])

  def testSetCoordsTerminalAtom(self):
    mol = Chem.MolFromMolBlock("""
     RDKit          2D

  6  6  0  0  0  0  0  0  0  0999 V2000
    1.5000    0.0000    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
    0.7500   -1.2990    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
   -0.7500   -1.2990    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
   -1.5000    0.0000    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
   -0.7500    1.2990    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
    0.7500    1.2990    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
  1  2  2  0
  2  3  1  0
  3  4  2  0
  4  5  1  0
  5  6  2  0
  6  1  1  0
M  END
""")
    mol = Chem.RWMol(mol)
    atom = Chem.Atom(0)
    idx = mol.AddAtom(atom)
    mol.AddBond(idx, 0)
    Chem.SetTerminalAtomCoords(mol, idx, 0)
    coord = mol.GetConformer().GetAtomPosition(idx)
    self.assertAlmostEqual(coord.x, 2.5, 2)
    self.assertAlmostEqual(coord.y, 0, 2)
    self.assertAlmostEqual(coord.z, 0, 2)

  def testSuppliersReadingDirectories(self):
    # this is an odd one, basically we need to check that we don't hang
    #  which is pretty much a bad test in my opinion, but YMMV
    d = tempfile.mkdtemp()
    self.assertTrue(os.path.exists(d))

    for supplier in [
        Chem.SmilesMolSupplier,
        Chem.SDMolSupplier,
        Chem.TDTMolSupplier,
        #Chem.CompressedSDMolSupplier,
    ]:
      print("supplier:", supplier)
      with self.assertRaises(OSError):
        suppl = supplier(d)
    if hasattr(Chem, 'MaeMolSupplier'):
      with self.assertRaises(OSError):
        suppl = Chem.MaeMolSupplier(d)
    os.rmdir(d)

  def testRandomSmilesVect(self):
    m = Chem.MolFromSmiles("C1OCC1N(CO)(Cc1ccccc1NCCl)")
    v = Chem.MolToRandomSmilesVect(m, 5, randomSeed=0xf00d)
    self.assertEqual(v, [
      "c1cc(CN(C2COC2)CO)c(cc1)NCCl", "N(CCl)c1c(CN(C2COC2)CO)cccc1", "N(CCl)c1ccccc1CN(C1COC1)CO",
      "OCN(Cc1ccccc1NCCl)C1COC1", "C(N(C1COC1)Cc1c(cccc1)NCCl)O"
    ])

    v = Chem.MolToRandomSmilesVect(m, 5, randomSeed=0xf00d, allHsExplicit=True)
    self.assertEqual(v, [
      "[cH]1[cH][c]([CH2][N]([CH]2[CH2][O][CH2]2)[CH2][OH])[c]([cH][cH]1)[NH][CH2][Cl]",
      "[NH]([CH2][Cl])[c]1[c]([CH2][N]([CH]2[CH2][O][CH2]2)[CH2][OH])[cH][cH][cH][cH]1",
      "[NH]([CH2][Cl])[c]1[cH][cH][cH][cH][c]1[CH2][N]([CH]1[CH2][O][CH2]1)[CH2][OH]",
      "[OH][CH2][N]([CH2][c]1[cH][cH][cH][cH][c]1[NH][CH2][Cl])[CH]1[CH2][O][CH2]1",
      "[CH2]([N]([CH]1[CH2][O][CH2]1)[CH2][c]1[c]([cH][cH][cH][cH]1)[NH][CH2][Cl])[OH]"
    ])

  def testGithub3198(self):

    ps = Chem.SmilesParserParams()
    ps.removeHs = False
    m = Chem.MolFromSmiles('[H]OC(F).[Na]', ps)

    qa = rdqueries.AAtomQueryAtom()
    self.assertEqual(tuple(x.GetIdx() for x in m.GetAtomsMatchingQuery(qa)), (1, 2, 3, 4))

    qa = rdqueries.AHAtomQueryAtom()
    self.assertEqual(tuple(x.GetIdx() for x in m.GetAtomsMatchingQuery(qa)), (0, 1, 2, 3, 4))

    qa = rdqueries.QAtomQueryAtom()
    self.assertEqual(tuple(x.GetIdx() for x in m.GetAtomsMatchingQuery(qa)), (1, 3, 4))

    qa = rdqueries.QHAtomQueryAtom()
    self.assertEqual(tuple(x.GetIdx() for x in m.GetAtomsMatchingQuery(qa)), (0, 1, 3, 4))

    qa = rdqueries.XAtomQueryAtom()
    self.assertEqual(tuple(x.GetIdx() for x in m.GetAtomsMatchingQuery(qa)), (3, ))

    qa = rdqueries.XHAtomQueryAtom()
    self.assertEqual(tuple(x.GetIdx() for x in m.GetAtomsMatchingQuery(qa)), (0, 3))

    qa = rdqueries.MAtomQueryAtom()
    self.assertEqual(tuple(x.GetIdx() for x in m.GetAtomsMatchingQuery(qa)), (4, ))

    qa = rdqueries.MHAtomQueryAtom()
    self.assertEqual(tuple(x.GetIdx() for x in m.GetAtomsMatchingQuery(qa)), (0, 4))

  def testAdjustQueryPropertiesFiveRings(self):
    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")

    nops.adjustConjugatedFiveRings = True
    nmol = Chem.AdjustQueryProperties(mol, nops)
    self.assertEqual(Chem.MolToSmarts(nmol), "[#6]1-,=,:[#6]-,=,:[#6]-,=,:[#7H]-,=,:[#6]-,=,:1")

  def testFindPotentialStereo(self):
    mol = Chem.MolFromSmiles('C[C@H](F)C=CC')
    si = Chem.FindPotentialStereo(mol)
    self.assertEqual(len(si), 2)
    self.assertEqual(si[0].type, Chem.StereoType.Atom_Tetrahedral)
    self.assertEqual(si[0].specified, Chem.StereoSpecified.Specified)
    self.assertEqual(si[0].centeredOn, 1)
    self.assertEqual(si[0].descriptor, Chem.StereoDescriptor.Tet_CCW)
    self.assertEqual(list(si[0].controllingAtoms), [0, 2, 3])
    self.assertEqual(si[1].type, Chem.StereoType.Bond_Double)
    self.assertEqual(si[1].specified, Chem.StereoSpecified.Unspecified)
    self.assertEqual(si[1].centeredOn, 3)
    self.assertEqual(si[1].descriptor, Chem.StereoDescriptor.NoValue)
    self.assertEqual(list(si[1].controllingAtoms),
                     [1, Chem.StereoInfo.NOATOM, 5, Chem.StereoInfo.NOATOM])

  def testNewFindMolChiralCenters(self):
    mol = Chem.MolFromSmiles('C[C@H](F)C=CC(F)Cl')
    ctrs = Chem.FindMolChiralCenters(mol, useLegacyImplementation=False)
    self.assertEqual(len(ctrs), 1)
    self.assertEqual(ctrs, [(1, 'S')])
    ctrs = Chem.FindMolChiralCenters(mol, useLegacyImplementation=False, includeCIP=False)
    self.assertEqual(len(ctrs), 1)
    self.assertEqual(ctrs, [(1, 'Tet_CCW')])
    ctrs = Chem.FindMolChiralCenters(mol, useLegacyImplementation=False, includeUnassigned=True,
                                     includeCIP=False)
    self.assertEqual(len(ctrs), 2)
    self.assertEqual(ctrs, [(1, 'Tet_CCW'), (5, '?')])
    ctrs = Chem.FindMolChiralCenters(mol, useLegacyImplementation=False, includeUnassigned=True,
                                     includeCIP=True)
    self.assertEqual(len(ctrs), 2)
    self.assertEqual(ctrs, [(1, 'S'), (5, '?')])

  def testGithub6945(self):
    origVal = Chem.GetUseLegacyStereoPerception()
    tgt = [(1, '?'), (4, 'R')]
    try:
      for opt in (not origVal, origVal):
        Chem.SetUseLegacyStereoPerception(opt)
        # make sure calling with the default value works:
        self.assertEqual(
          tgt,
          Chem.FindMolChiralCenters(Chem.MolFromSmiles('FC(Cl)(Br)[C@H](F)Cl'),
                                    includeUnassigned=True))
        for useLegacy in (True, False):
          self.assertEqual(
            tgt,
            Chem.FindMolChiralCenters(Chem.MolFromSmiles('FC(Cl)(Br)[C@H](F)Cl'),
                                      includeUnassigned=True, useLegacyImplementation=useLegacy))
    except:
      raise
    finally:
      Chem.SetUseLegacyStereoPerception(origVal)

  @unittest.skipUnless(hasattr(Chem, 'MolFromPNGFile'), "RDKit not built with iostreams support")
  def testMolFromPNG(self):
    fileN = os.path.join(RDConfig.RDBaseDir, 'Code', 'GraphMol', 'FileParsers', 'test_data',
                         'colchicine.png')
    mol = Chem.MolFromPNGFile(fileN)
    self.assertIsNotNone(mol)
    self.assertEqual(mol.GetNumAtoms(), 29)

    with open(fileN, 'rb') as inf:
      d = inf.read()
    mol = Chem.MolFromPNGString(d)
    self.assertIsNotNone(mol)
    self.assertEqual(mol.GetNumAtoms(), 29)

  @unittest.skipUnless(hasattr(Chem, 'MolFromPNGFile'), "RDKit not built with iostreams support")
  def testMolToPNG(self):
    fileN = os.path.join(RDConfig.RDBaseDir, 'Code', 'GraphMol', 'FileParsers', 'test_data',
                         'colchicine.no_metadata.png')

    with open(fileN, 'rb') as inf:
      d = inf.read()
    mol = Chem.MolFromSmiles("COc1cc2c(c(OC)c1OC)-c1ccc(OC)c(=O)cc1[C@@H](NC(C)=O)CC2")
    self.assertIsNotNone(mol)
    self.assertEqual(mol.GetNumAtoms(), 29)

    nd = Chem.MolMetadataToPNGString(mol, d)
    mol = Chem.MolFromPNGString(nd)
    self.assertIsNotNone(mol)
    self.assertEqual(mol.GetNumAtoms(), 29)

    nd = Chem.MolMetadataToPNGFile(mol, fileN)
    mol = Chem.MolFromPNGString(nd)
    self.assertIsNotNone(mol)
    self.assertEqual(mol.GetNumAtoms(), 29)

  @unittest.skipUnless(hasattr(Chem, 'MolFromPNGFile'), "RDKit not built with iostreams support")
  def testMolsFromPNG(self):
    refMols = [Chem.MolFromSmiles(x) for x in ('c1ccccc1', 'CCO', 'CC(=O)O', 'c1ccccn1')]
    fileN = os.path.join(RDConfig.RDBaseDir, 'Code', 'GraphMol', 'FileParsers', 'test_data',
                         'multiple_mols.png')
    mols = Chem.MolsFromPNGFile(fileN)
    self.assertEqual(len(mols), len(refMols))
    for mol, refMol in zip(mols, refMols):
      self.assertEqual(Chem.MolToSmiles(mol), Chem.MolToSmiles(refMol))

  @unittest.skipUnless(hasattr(Chem, 'MolFromPNGFile'), "RDKit not built with iostreams support")
  def testMetadataToPNG(self):
    fileN = os.path.join(RDConfig.RDBaseDir, 'Code', 'GraphMol', 'FileParsers', 'test_data',
                         'colchicine.png')

    with open(fileN, 'rb') as inf:
      d = inf.read()
    mol = Chem.MolFromPNGString(d)
    nd = Chem.MolMetadataToPNGString(mol, d)
    vals = {'foo': '1', 'bar': '2'}
    nd = Chem.AddMetadataToPNGString(vals, nd)
    nvals = Chem.MetadataFromPNGString(nd)
    self.assertTrue('foo' in nvals)
    self.assertEqual(nvals['foo'], b'1')
    self.assertTrue('bar' in nvals)
    self.assertEqual(nvals['bar'], b'2')

    nd = Chem.AddMetadataToPNGFile(vals, fileN)
    nvals = Chem.MetadataFromPNGString(nd)
    self.assertTrue('foo' in nvals)
    self.assertEqual(nvals['foo'], b'1')
    self.assertTrue('bar' in nvals)
    self.assertEqual(nvals['bar'], b'2')

    vals = {'foo': 1, 'bar': '2'}
    with self.assertRaises(TypeError):
      nd = Chem.AddMetadataToPNGString(vals, d)

  def test_github3403(self):
    core1 = "[$(C-!@[a])](=O)(Cl)"
    sma = Chem.MolFromSmarts(core1)

    m = Chem.MolFromSmiles("c1ccccc1C(=O)Cl")
    self.assertFalse(m.HasSubstructMatch(sma, recursionPossible=False))

    m = Chem.MolFromSmiles("c1ccccc1C(=O)Cl")
    self.assertTrue(m.HasSubstructMatch(sma))

    m = Chem.MolFromSmiles("c1ccccc1C(=O)Cl")
    self.assertFalse(m.HasSubstructMatch(sma, recursionPossible=False))

  def test_github3517(self):
    fileN = os.path.join(RDConfig.RDBaseDir, 'Code', 'GraphMol', 'FileParsers', 'test_data',
                         'NCI_aids_few.sdf')
    sdSup = Chem.SDMolSupplier(fileN)
    mols_list = list(sdSup)
    mols_list_compr = [m for m in sdSup]
    self.assertEqual(len(mols_list), len(mols_list_compr))

  def test_github3492(self):

    def read_smile(s):
      m = Chem.MolFromSmiles(s)
      rdkit.Chem.rdDepictor.Compute2DCoords(m)
      return m

    def sq_dist(a, b):
      ab = [a[i] - b[i] for i, _ in enumerate(a)]
      return sum([d * d for d in ab])

    self.assertIsNotNone(Chem.MolFromSmiles("OCCN").GetAtoms()[0].GetOwningMol())
    self.assertEqual([Chem.MolFromSmiles("OCCN").GetAtoms()[i].GetAtomicNum() for i in range(4)],
                     [8, 6, 6, 7])
    self.assertIsNotNone(Chem.MolFromSmiles("O=CCC=N").GetBonds()[0].GetOwningMol())
    self.assertEqual(
      [Chem.MolFromSmiles("O=CCC=N").GetBonds()[i].GetBondType() for i in range(4)],
      [Chem.BondType.DOUBLE, Chem.BondType.SINGLE, Chem.BondType.SINGLE, Chem.BondType.DOUBLE])
    self.assertIsNotNone(read_smile("CCC").GetConformers()[0].GetOwningMol())
    pos = read_smile("CCC").GetConformers()[0].GetPositions()
    self.assertAlmostEqual(sq_dist(pos[0], pos[1]), sq_dist(pos[1], pos[2]))

  def test_get_set_positions(self):
    m = Chem.MolFromSmiles('CCC |(-1.29904,-0.25,;0,0.5,;1.29904,-0.25,)|')
    pos = np.zeros([3,3], np.double)
    pos[0][1] = 1
    pos[0][2] = 2
    pos[1][0] = 3
    pos[1][1] = 4
    pos[1][2] = 5
    pos[2][0] = 6
    pos[2][1] = 6
    pos[2][2] = 7

    m.GetConformer(0).SetPositions(pos)
    pos2 = m.GetConformer(0).GetPositions()

    self.assertTrue( (pos==pos2).all())


  def test_get_set_positions_stride(self):
    m = Chem.MolFromSmiles('CCC |(-1.29904,-0.25,;0,0.5,;1.29904,-0.25,)|')
    # to check stride walking in SetPositions
    # allocate a double size array, then slice every other element
    # numpy will mess with the striding to create the view onto the double size array
    pos = np.zeros([6,3], np.double)[::2]
    pos[0][1] = 1
    pos[0][2] = 2
    pos[1][0] = 3
    pos[1][1] = 4
    pos[1][2] = 5
    pos[2][0] = 6
    pos[2][1] = 6
    pos[2][2] = 7

    m.GetConformer(0).SetPositions(pos)
    pos2 = m.GetConformer(0).GetPositions()

    self.assertTrue( (pos==pos2).all())


  def test_github3553(self):
    fileN = os.path.join(RDConfig.RDBaseDir, 'Code', 'GraphMol', 'Wrap', 'test_data',
                         'github3553.sdf')
    sdSup = Chem.SDMolSupplier(fileN)
    for mol in sdSup:
      pval = mol.GetProp('boiling.point.predicted')
      sio = StringIO()
      w = Chem.SDWriter(sio)
      w.SetKekulize(True)
      w.SetForceV3000(True)
      w.write(mol)
      w.flush()
      txt = sio.getvalue()
      self.assertTrue(pval in txt)

  def test_github1631(self):
    fileN = os.path.join(RDConfig.RDBaseDir, 'Code', 'GraphMol', 'FileParsers', 'test_data',
                         '1CRN.pdb')

    m = Chem.MolFromPDBFile(fileN)
    info = m.GetAtomWithIdx(0).GetPDBResidueInfo()
    self.assertEqual(info.GetName(), " N  ")
    self.assertEqual(info.GetResidueName(), "THR")
    self.assertAlmostEqual(info.GetTempFactor(), 13.79, 2)

    m2 = Chem.MolFromSmiles('CC(C(C(=O)O)N)O')
    self.assertTrue(m2.GetAtomWithIdx(6).GetPDBResidueInfo() is None)
    m2.GetAtomWithIdx(6).SetPDBResidueInfo(info)
    info2 = m2.GetAtomWithIdx(6).GetPDBResidueInfo()
    self.assertEqual(info2.GetName(), " N  ")
    self.assertEqual(info2.GetResidueName(), "THR")
    self.assertAlmostEqual(info2.GetTempFactor(), 13.79, 2)

  def testMolzip(self):
    tests = [["C[*:1]", "N[*:1]", "CN", Chem.MolzipParams()]]
    for a, b, res, params in tests:
      self.assertEqual(
        Chem.CanonSmiles(res),
        Chem.MolToSmiles(Chem.molzip(Chem.MolFromSmiles(a), Chem.MolFromSmiles(b), params)))

    # multiple arg test
    a = Chem.MolFromSmiles('C=C[1*]')
    b = Chem.MolFromSmiles('O/C=N/[1*]')
    p = Chem.MolzipParams()
    p.label = Chem.MolzipLabel.Isotope
    c = Chem.molzip(a, b, p)
    self.assertEqual(Chem.MolToSmiles(c), 'C=C/N=C/O')

    # single argument test
    a = Chem.MolFromSmiles('C=C[1*].O/C=N/[1*]')
    p = Chem.MolzipParams()
    p.label = Chem.MolzipLabel.Isotope
    c = Chem.molzip(a, p)
    self.assertEqual(Chem.MolToSmiles(c), 'C=C/N=C/O')

    a = Chem.MolFromSmiles("[C@H]([Xe])(F)([V])")
    b = Chem.MolFromSmiles("[Xe]N.[V]I")
    p = Chem.MolzipParams()
    p.label = Chem.MolzipLabel.AtomType
    p.setAtomSymbols(["Xe", "V"])
    c = Chem.molzip(a, b, p)
    self.assertEqual(Chem.MolToSmiles(c), "N[C@@H](F)I")

    a = Chem.MolFromSmiles("C(=[*:1])N")
    b = Chem.MolFromSmiles("[*:1]-N=C")
    p = Chem.MolzipParams()
    p.enforceValenceRules = False
    c = Chem.molzip(a, b, p)

  def testContextManagers(self):
    from rdkit import RDLogger
    RDLogger.DisableLog('rdApp.*')
    fileN = os.path.join(RDConfig.RDBaseDir, 'Code', 'GraphMol', 'Wrap', 'test_data',
                         'github3553.sdf')
    with Chem.SDMolSupplier(fileN) as suppl:
      mols = [x for x in suppl if x is not None]
    sio = StringIO()
    with Chem.SDWriter(sio) as w:
      for m in mols:
        w.write(m)
    txt = sio.getvalue()
    self.assertEqual(txt.count('$$$$'), len(mols))
    with self.assertRaises(RuntimeError):
      w.write(mols[0])

    with Chem.ForwardSDMolSupplier(fileN) as suppl:
      mols = [x for x in suppl if x is not None]

    fileN = os.path.join(RDConfig.RDBaseDir, 'Code', 'GraphMol', 'FileParsers', 'test_data',
                         'first_200.tpsa.csv')
    with Chem.SmilesMolSupplier(fileN, ",", 0, -1) as suppl:
      ms = [x for x in suppl if x is not None]

    sio = StringIO()
    with Chem.SmilesWriter(sio) as w:
      for m in mols:
        w.write(m)
    txt = sio.getvalue()
    self.assertEqual(txt.count('\n'), len(mols) + 1)
    with self.assertRaises(RuntimeError):
      w.write(mols[0])

    data = """$SMI<Cc1nnc(N)nc1C>
CAS<17584-12-2>
|
$SMI<Cc1n[nH]c(=O)nc1N>
CAS<~>
|
$SMI<Cc1n[nH]c(=O)[nH]c1=O>
CAS<932-53-6>
|
$SMI<Cc1nnc(NN)nc1O>
CAS<~>
|"""
    with Chem.TDTMolSupplier() as suppl:
      suppl.SetData(data, "CAS")
      ms = [x for x in suppl if x is not None]
    sio = StringIO()
    with Chem.TDTWriter(sio) as w:
      for m in mols:
        w.write(m)
    txt = sio.getvalue()
    self.assertEqual(txt.count('$SMI'), len(mols))
    with self.assertRaises(RuntimeError):
      w.write(mols[0])

    fileN = os.path.join(RDConfig.RDBaseDir, 'Code', 'GraphMol', 'FileParsers', 'test_data',
                         '1CRN.pdb')
    m = Chem.MolFromPDBFile(fileN)
    mols = [m, m]
    sio = StringIO()
    with Chem.PDBWriter(sio) as w:
      for m in mols:
        w.write(m)
    txt = sio.getvalue()
    self.assertEqual(txt.count('COMPND    CRAMBIN'), len(mols))
    with self.assertRaises(RuntimeError):
      w.write(mols[0])

    if hasattr(Chem, 'MaeMolSupplier'):
      fileN = os.path.join(RDConfig.RDBaseDir, 'Code', 'GraphMol', 'FileParsers', 'test_data',
                           'NCI_aids_few.mae')
      with Chem.MaeMolSupplier(fileN) as suppl:
        ms = [x for x in suppl if x is not None]

    RDLogger.EnableLog('rdApp.*')

  def testInsertMol(self):
    m = Chem.MolFromSmiles("CNO")
    m2 = Chem.MolFromSmiles("c1ccccc1")
    m3 = Chem.MolFromSmiles("C1CC1")

    rwmol = Chem.RWMol(m)
    rwmol.InsertMol(m2)
    rwmol.InsertMol(m3)
    self.assertEqual(Chem.MolToSmiles(rwmol), Chem.CanonSmiles("CNO.c1ccccc1.C1CC1"))

  def testBatchEdits(self):
    mol = Chem.MolFromSmiles("C1CCCO1")

    for rwmol in [Chem.EditableMol(mol), Chem.RWMol(mol)]:
      rwmol.BeginBatchEdit()
      rwmol.RemoveAtom(2)
      rwmol.RemoveAtom(3)
      rwmol.CommitBatchEdit()
      nmol = rwmol.GetMol()
      self.assertEqual(Chem.MolToSmiles(nmol), "CCO")

    for rwmol in [Chem.EditableMol(mol), Chem.RWMol(mol)]:
      rwmol = Chem.EditableMol(mol)
      rwmol.BeginBatchEdit()
      rwmol.RemoveAtom(3)
      rwmol.RemoveBond(4, 0)
      rwmol.CommitBatchEdit()
      nmol = rwmol.GetMol()
      self.assertEqual(Chem.MolToSmiles(nmol), "CCC.O")

    for rwmol in [Chem.EditableMol(mol), Chem.RWMol(mol)]:
      rwmol.BeginBatchEdit()
      rwmol.RemoveAtom(2)
      rwmol.RemoveAtom(3)
      rwmol.RollbackBatchEdit()
      nmol = rwmol.GetMol()
      self.assertEqual(Chem.MolToSmiles(nmol), "C1CCOC1")

  def testBatchEditContextManager(self):
    mol = Chem.MolFromSmiles("C1CCCO1")
    with Chem.RWMol(mol) as rwmol:
      rwmol.RemoveAtom(2)
      rwmol.RemoveAtom(3)
      # make sure we haven't actually changed anything yet:
      self.assertEqual(rwmol.GetNumAtoms(), mol.GetNumAtoms())
    self.assertEqual(rwmol.GetNumAtoms(), mol.GetNumAtoms() - 2)

    # make sure no changes get made if we throw an exception
    try:
      with Chem.RWMol(mol) as rwmol:
        rwmol.RemoveAtom(2)
        rwmol.RemoveAtom(6)
    except Exception:
      pass
    self.assertEqual(rwmol.GetNumAtoms(), mol.GetNumAtoms())

  def testGithub4138(self):
    m = Chem.MolFromSmiles('C1CCCO1')
    q = Chem.MolFromSmarts('')
    self.assertFalse(m.HasSubstructMatch(q))
    self.assertEqual(m.GetSubstructMatch(q), ())
    self.assertEqual(m.GetSubstructMatches(q), ())

    m = Chem.MolFromSmiles('')
    q = Chem.MolFromSmarts('C')
    self.assertFalse(m.HasSubstructMatch(q))
    self.assertEqual(m.GetSubstructMatch(q), ())
    self.assertEqual(m.GetSubstructMatches(q), ())

  def testGithub4144(self):
    ''' the underlying problem with #4144 was that the
    includeRings argument could not be passed to ClearComputedProps()
    from Python. Make sure that's fixed
    '''
    m = Chem.MolFromSmiles('c1ccccc1')
    self.assertEqual(m.GetRingInfo().NumRings(), 1)
    m.ClearComputedProps(includeRings=False)
    self.assertEqual(m.GetRingInfo().NumRings(), 1)
    m.ClearComputedProps()
    with self.assertRaises(RuntimeError):
      m.GetRingInfo().NumRings()

  def testAddWavyBondsForStereoAny(self):
    m = Chem.MolFromSmiles('CC=CC')
    m.GetBondWithIdx(1).SetStereo(Chem.BondStereo.STEREOANY)
    m2 = Chem.Mol(m)
    Chem.AddWavyBondsForStereoAny(m2)
    self.assertEqual(m2.GetBondWithIdx(1).GetStereo(), Chem.BondStereo.STEREONONE)
    self.assertEqual(m2.GetBondWithIdx(0).GetBondDir(), Chem.BondDir.UNKNOWN)
    m2 = Chem.Mol(m)
    Chem.AddWavyBondsForStereoAny(m2, clearDoubleBondFlags=False)
    self.assertEqual(m2.GetBondWithIdx(1).GetStereo(), Chem.BondStereo.STEREOANY)
    self.assertEqual(m2.GetBondWithIdx(0).GetBondDir(), Chem.BondDir.UNKNOWN)

    m = Chem.MolFromSmiles("CC=CC=CC=CC")
    m.GetBondWithIdx(1).SetStereoAtoms(0, 3)
    m.GetBondWithIdx(1).SetStereo(Chem.BondStereo.STEREOCIS)
    m.GetBondWithIdx(3).SetStereo(Chem.BondStereo.STEREOANY)
    m.GetBondWithIdx(5).SetStereoAtoms(4, 7)
    m.GetBondWithIdx(5).SetStereo(Chem.BondStereo.STEREOCIS)
    m2 = Chem.Mol(m)
    Chem.AddWavyBondsForStereoAny(m2)
    self.assertEqual(m2.GetBondWithIdx(3).GetStereo(), Chem.BondStereo.STEREOANY)
    self.assertEqual(m2.GetBondWithIdx(0).GetBondDir(), Chem.BondDir.NONE)

    Chem.AddWavyBondsForStereoAny(
      m2, addWhenImpossible=Chem.StereoBondThresholds.DBL_BOND_SPECIFIED_STEREO)
    self.assertEqual(m2.GetBondWithIdx(3).GetStereo(), Chem.BondStereo.STEREONONE)
    self.assertEqual(m2.GetBondWithIdx(2).GetBondDir(), Chem.BondDir.UNKNOWN)

  def testSmartsParseParams(self):
    smi = "CCC |$foo;;bar$| ourname"
    m = Chem.MolFromSmarts(smi)
    self.assertTrue(m is not None)
    ps = Chem.SmartsParserParams()
    ps.allowCXSMILES = False
    ps.parseName = False
    m = Chem.MolFromSmarts(smi, ps)
    self.assertTrue(m is None)
    ps.allowCXSMILES = True
    ps.parseName = True
    m = Chem.MolFromSmarts(smi, ps)
    self.assertTrue(m is not None)
    self.assertTrue(m.GetAtomWithIdx(0).HasProp('atomLabel'))
    self.assertEqual(m.GetAtomWithIdx(0).GetProp('atomLabel'), "foo")
    self.assertTrue(m.HasProp('_Name'))
    self.assertEqual(m.GetProp('_Name'), "ourname")
    self.assertEqual(m.GetProp("_CXSMILES_Data"), "|$foo;;bar$|")

  def testSmilesWriteParams(self):
    m = Chem.MolFromSmiles('C[C@H](F)Cl')
    ps = Chem.SmilesWriteParams()
    ps.rootedAtAtom = 1
    self.assertEqual(Chem.MolToSmiles(m, ps), "[C@@H](C)(F)Cl")
    self.assertEqual(Chem.MolToCXSmiles(m, ps), "[C@@H](C)(F)Cl")

  def testCXSmilesOptions(self):
    # just checking that the fields parameter gets used.
    # we've tested the individual values on the C++ side
    m = Chem.MolFromSmiles("OC1CCC(F)C1 |LN:1:1.3.2.6|")
    ps = Chem.SmilesWriteParams()
    ps.rootedAtAtom = 1
    self.assertEqual(
      Chem.MolToCXSmiles(m, ps, (Chem.CXSmilesFields.CX_ALL ^ Chem.CXSmilesFields.CX_LINKNODES)),
      "C1(O)CCC(F)C1")
    self.assertTrue(hasattr(Chem.CXSmilesFields, 'CX_BOND_CFG'))
    self.assertTrue(hasattr(Chem.CXSmilesFields, 'CX_ALL_BUT_COORDS'))

  def testKekulizeIfPossible(self):
    m = Chem.MolFromSmiles('c1cccn1', sanitize=False)
    m.UpdatePropertyCache(strict=False)
    Chem.KekulizeIfPossible(m)
    for atom in m.GetAtoms():
      self.assertTrue(atom.GetIsAromatic())
    for bond in m.GetBonds():
      self.assertTrue(bond.GetIsAromatic())
      self.assertEqual(bond.GetBondType(), Chem.BondType.AROMATIC)

  def testHasValenceViolation(self):
    mol = Chem.MolFromSmiles('C(C)(C)(C)(C)C', sanitize=False)
    mol.UpdatePropertyCache(strict=False)
    self.assertTrue(any(a.HasValenceViolation() for a in mol.GetAtoms()))

  def testgithub4992(self):
    if not hasattr(Chem, "Chem.MultithreadedSDMolSupplier"):
      return

    good1 = Chem.MolFromSmiles('C')
    #good1.SetProp('molname', 'good1')
    good2 = Chem.MolFromSmiles('CC')
    #good2.SetProp('molname', 'good2')
    good3 = Chem.MolFromSmiles('CCC')
    #good3.SetProp('molname', 'good3')
    bad = Chem.MolFromSmiles('CN(C)(C)C', sanitize=False)
    #bad.SetProp('molname', 'bad')

    with Chem.SDWriter("good1_good2_good3.sdf") as w:
      w.write(good1)
      w.write(good2)
      w.write(good3)
      w.write(good1)
      w.write(good2)
      w.write(good3)

    with Chem.SDWriter("good1_good2_good3_bad.sdf") as w:
      w.write(good1)
      w.write(good2)
      w.write(good3)
      w.write(bad)

    with Chem.SDWriter("good1_good2_bad_good3.sdf") as w:
      w.write(good1)
      w.write(good2)
      w.write(bad)
      w.write(good3)

    with Chem.SDWriter("bad_good1_good2_good3.sdf") as w:
      w.write(bad)
      w.write(good1)
      w.write(good2)
      w.write(good3)

    def read_mols(supplier, filename):
      i = 0
      with supplier(filename) as sdSuppl:
        count = -1
        for count, mol in enumerate(sdSuppl):
          if mol is not None:
            i += 1
      return i

    counts = []
    for i, s in enumerate((Chem.SDMolSupplier, Chem.MultithreadedSDMolSupplier)):
      for j, f in enumerate(('good1_good2_good3.sdf', 'good1_good2_bad_good3.sdf',
                             'good1_good2_bad_good3.sdf', 'bad_good1_good2_good3.sdf')):
        print(f'---------------\n{s.__name__} {f}')
        if i == 0:
          counts.append(read_mols(s, f))
        else:
          self.assertEqual(counts[j], read_mols(s, f))

  def test_blocklogs(self):
    with capture_logging(logging.INFO) as log_stream:

      # Logging is known to be problematic with static linked libs
      # so let's skip the test if we can't see the expected error
      # log before we block logging.
      Chem.MolFromSmiles('garbage_0')
      if 'garbage_0' not in log_stream.getvalue():
        self.skipTest('cannot fetch log msgs')
      else:
        log_stream.truncate(0)

      with rdBase.BlockLogs():
        Chem.MolFromSmiles('garbage_1')
      self.assertNotIn('garbage_1', log_stream.getvalue())

      Chem.MolFromSmiles('garbage_2')
      self.assertIn('garbage_2', log_stream.getvalue())
      log_stream.truncate(0)

      block = rdBase.BlockLogs()
      self.assertIsNotNone(block)

      Chem.MolFromSmiles('garbage_3')
      self.assertNotIn('garbage_3', log_stream.getvalue())

      del block

      Chem.MolFromSmiles('garbage_4')
      self.assertIn('garbage_4', log_stream.getvalue())
      log_stream.truncate(0)

  def testDisableNontetrahedralStereo(self):
    fileN = os.path.join(RDConfig.RDBaseDir, 'Code', 'GraphMol', 'test_data',
                         'nontetrahedral_3d.sdf')
    origVal = Chem.GetAllowNontetrahedralChirality()
    Chem.SetAllowNontetrahedralChirality(True)
    suppl = Chem.SDMolSupplier(fileN, sanitize=False)
    for mol in suppl:
      Chem.AssignStereochemistryFrom3D(mol)
      ct = mol.GetProp("ChiralType")
      at = mol.GetAtomWithIdx(0)
      if ct == "SP":
        self.assertEqual(at.GetChiralTag(), Chem.ChiralType.CHI_SQUAREPLANAR)
      elif ct == "TB":
        self.assertEqual(at.GetChiralTag(), Chem.ChiralType.CHI_TRIGONALBIPYRAMIDAL)
      elif ct == "OH":
        self.assertEqual(at.GetChiralTag(), Chem.ChiralType.CHI_OCTAHEDRAL)
      elif ct == "TH":
        self.assertEqual(at.GetChiralTag(), Chem.ChiralType.CHI_TETRAHEDRAL)
    Chem.SetAllowNontetrahedralChirality(False)
    suppl = Chem.SDMolSupplier(fileN, sanitize=False)
    for mol in suppl:
      Chem.AssignStereochemistryFrom3D(mol)
      ct = mol.GetProp("ChiralType")
      at = mol.GetAtomWithIdx(0)
      if ct == "TH":
        self.assertEqual(at.GetChiralTag(), Chem.ChiralType.CHI_TETRAHEDRAL)
      else:
        self.assertEqual(at.GetChiralTag(), Chem.ChiralType.CHI_UNSPECIFIED)
    Chem.SetAllowNontetrahedralChirality(origVal)

  def test_legacyStereochemGlobal(self):
    origVal = Chem.GetUseLegacyStereoPerception()
    Chem.SetUseLegacyStereoPerception(True)
    m = Chem.MolFromSmiles("C[C@H]1CCC2(CC1)CC[C@H](C)C(C)C2")
    self.assertEqual(m.GetAtomWithIdx(1).GetChiralTag(), Chem.ChiralType.CHI_UNSPECIFIED)
    self.assertNotEqual(m.GetAtomWithIdx(9).GetChiralTag(), Chem.ChiralType.CHI_UNSPECIFIED)

    Chem.SetUseLegacyStereoPerception(False)
    m = Chem.MolFromSmiles("C[C@H]1CCC2(CC1)CC[C@H](C)C(C)C2")
    self.assertEqual(m.GetAtomWithIdx(1).GetChiralTag(), Chem.ChiralType.CHI_UNSPECIFIED)
    self.assertNotEqual(m.GetAtomWithIdx(9).GetChiralTag(), Chem.ChiralType.CHI_UNSPECIFIED)

    Chem.SetUseLegacyStereoPerception(origVal)

  def testValidationMrvRxn(self):
    Chem.SetUseLegacyStereoPerception(False)

    mrvBlock = ""
    fileN = os.path.join(RDConfig.RDBaseDir, 'Code', 'GraphMol', 'Wrap', 'test_data',
                         'DoubleBondRxn.mrv')
    with open(fileN) as f:
      mrvBlock = f.read()
    rxn = AllChem.ReactionFromMrvBlock(mrvBlock, True, True)

    smi = AllChem.ReactionToSmiles(rxn)
    self.assertEqual(smi, "CC=C1COC(C)OC1>>CC=C1COC(C)OC1")

    rxn = AllChem.ReactionFromMrvFile(fileN, True, True)
    smi = AllChem.ReactionToSmiles(rxn)
    self.assertEqual(smi, "CC=C1COC(C)OC1>>CC=C1COC(C)OC1")

    Chem.SetUseLegacyStereoPerception(True)

  def testValidationMrvRxn2(self):
    Chem.SetUseLegacyStereoPerception(False)

    mrvBlock = ""
    fileN = os.path.join(RDConfig.RDBaseDir, 'Code', 'GraphMol', 'Wrap', 'test_data',
                         'DoubleBondRxn2.mrv')

    with open(fileN) as f:
      mrvBlock = f.read()
    rxn = AllChem.ReactionFromMrvBlock(mrvBlock, True, True)

    smi = AllChem.ReactionToSmiles(rxn)
    self.assertEqual(
      smi,
      "C=C1CC(C)(C(C)(C)C)CC(=C)C1=Cc1cc(C)c(C)c(CCC)c1>>C=C1CC(C)(C(C)(C)C)CC(=C)C1=Cc1cc(C)c(Cl)c(CCC)c1"
    )

    Chem.SetUseLegacyStereoPerception(True)

  def testValidationMrvRxn3(self):
    Chem.SetUseLegacyStereoPerception(False)

    mrvBlock = ""
    fileN = os.path.join(RDConfig.RDBaseDir, 'Code', 'GraphMol', 'Wrap', 'test_data',
                         'DoubleBondAndChiralRxn.mrv')
    with open(fileN) as f:
      mrvBlock = f.read()
    rxn = AllChem.ReactionFromMrvBlock(mrvBlock, True, True)

    smi = AllChem.ReactionToSmiles(rxn)
    self.assertEqual(
      smi,
      "C=C1C[C@](C)(C(C)(C)C)CC(=C)/C1=C/c1cc(C)c(C)c(CCC)c1>>C=C1CC(C)(C(C)(C)C)CC(=C)C1=Cc1cc(C)c(Cl)c(C[C@H](C)Cl)c1"
    )

    Chem.SetUseLegacyStereoPerception(True)

  def testValidationRxn(self):
    Chem.SetUseLegacyStereoPerception(False)

    rxnBlock = ""
    fileN = os.path.join(RDConfig.RDBaseDir, 'Code', 'GraphMol', 'Wrap', 'test_data',
                         'DoubleBondAndChiralRxn.rxn')
    with open(fileN) as f:
      rxnBlock = f.read()

    rxn = AllChem.ReactionFromRxnBlock(rxnBlock, True, True, False)
    smi = AllChem.ReactionToSmiles(rxn)
    self.assertEqual(
      smi,
      "C=C1C[C@](C)(C(C)(C)C)CC(=C)/C1=C/c1cc(C)c(C)c(CCC)c1>>C=C1CC(C)(C(C)(C)C)CC(=C)C1=Cc1cc(C)c(Cl)c(C[C@H](C)Cl)c1"
    )

    rxn = AllChem.ReactionFromRxnFile(fileN, True, True, False)

    smi = AllChem.ReactionToSmiles(rxn)
    self.assertEqual(
      smi,
      "C=C1C[C@](C)(C(C)(C)C)CC(=C)/C1=C/c1cc(C)c(C)c(CCC)c1>>C=C1CC(C)(C(C)(C)C)CC(=C)C1=Cc1cc(C)c(Cl)c(C[C@H](C)Cl)c1"
    )

    Chem.SetUseLegacyStereoPerception(True)

  def testValidationSmiles(self):
    Chem.SetUseLegacyStereoPerception(False)

    smi = "C=C1CC(C)(C(C)(C)C)CC(=C)/C1=C/c1cc(C)c(Cl)c(C[C@H](C)Cl)c1"
    ps = Chem.SmilesParserParams()
    ps.allowCXSMILES = True
    ps.parseName = False
    ps.sanitize = True
    ps.removeHs = False
    mol = Chem.MolFromSmiles(smi, ps)
    self.assertIsNotNone(mol)

    of = Chem.SmilesWriteParams()
    outSmi = Chem.MolToCXSmiles(mol, of, Chem.CXSmilesFields.CX_ALL)

    self.assertEqual(outSmi, "C=C1CC(C)(C(C)(C)C)CC(=C)C1=Cc1cc(C)c(Cl)c(C[C@H](C)Cl)c1")

    smi = "C=C1C[C@](C)(C(C)(C)C)CC(=C)/C1=C/c1cc(C)c(Cl)c(C[C@H](C)Cl)c1"
    mol = Chem.MolFromSmiles(smi, ps)
    self.assertIsNotNone(mol)

    outSmi = Chem.MolToCXSmiles(mol, of, Chem.CXSmilesFields.CX_ALL),

    self.assertEqual(outSmi[0], "C=C1C[C@@](C)(C(C)(C)C)CC(=C)/C1=C\\c1cc(C)c(Cl)c(C[C@H](C)Cl)c1")

    Chem.SetUseLegacyStereoPerception(True)

  def testValidationMol(self):
    Chem.SetUseLegacyStereoPerception(False)
    molBlock = ""
    fileN = os.path.join(RDConfig.RDBaseDir, 'Code', 'GraphMol', 'Wrap', 'test_data',
                         'DoubleBond2000.mol')
    with open(fileN) as f:
      molBlock = f.read()
    mol = Chem.MolFromMolBlock(molBlock, True, True, False)

    smi = Chem.MolToSmiles(mol)
    self.assertEqual(smi, "C=C1CC(C)(C(C)(C)C)CC(=C)C1=Cc1cc(C)c(Cl)c(C[C@H](C)Cl)c1")

    rxn = Chem.MolFromMolFile(fileN, True, True, False)

    smi = Chem.MolToSmiles(rxn)
    self.assertEqual(smi, "C=C1CC(C)(C(C)(C)C)CC(=C)C1=Cc1cc(C)c(Cl)c(C[C@H](C)Cl)c1")

    # nowthe V3000 version

    fileN = os.path.join(RDConfig.RDBaseDir, 'Code', 'GraphMol', 'Wrap', 'test_data',
                         'DoubleBond3000.mol')
    with open(fileN) as f:
      molBlock = f.read()
    mol = Chem.MolFromMolBlock(molBlock, True, True, False)

    smi = Chem.MolToSmiles(mol)
    self.assertEqual(smi, "C=C1CC(C)(C(C)(C)C)CC(=C)C1=Cc1cc(C)c(Cl)c(C[C@H](C)Cl)c1")

    mol = Chem.MolFromMolFile(fileN, True, True, False)
    smi = Chem.MolToSmiles(mol)
    self.assertEqual(smi, "C=C1CC(C)(C(C)(C)C)CC(=C)C1=Cc1cc(C)c(Cl)c(C[C@H](C)Cl)c1")

    # now the chiral versions - the double bond should be chiral

    fileN = os.path.join(RDConfig.RDBaseDir, 'Code', 'GraphMol', 'Wrap', 'test_data',
                         'DoubleBond2000Chiral.mol')
    with open(fileN) as f:
      molBlock = f.read()
    mol = Chem.MolFromMolBlock(molBlock, True, True, False)

    smi = Chem.MolToSmiles(mol)
    self.assertEqual(smi, "C=C1C[C@@](C)(C(C)(C)C)CC(=C)/C1=C/c1cc(C)c(Cl)c(C[C@H](C)Cl)c1")

    rxn = Chem.MolFromMolFile(fileN, True, True, False)

    smi = Chem.MolToSmiles(rxn)
    self.assertEqual(smi, "C=C1C[C@@](C)(C(C)(C)C)CC(=C)/C1=C/c1cc(C)c(Cl)c(C[C@H](C)Cl)c1")

    # now the V3000 version

    fileN = os.path.join(RDConfig.RDBaseDir, 'Code', 'GraphMol', 'Wrap', 'test_data',
                         'DoubleBond3000Chiral.mol')
    with open(fileN) as f:
      molBlock = f.read()
    mol = Chem.MolFromMolBlock(molBlock, True, True, False)

    smi = Chem.MolToSmiles(mol)
    self.assertEqual(smi, "C=C1C[C@@](C)(C(C)(C)C)CC(=C)/C1=C/c1cc(C)c(Cl)c(C[C@H](C)Cl)c1")

    mol = Chem.MolFromMolFile(fileN, True, True, False)
    smi = Chem.MolToSmiles(mol)
    self.assertEqual(smi, "C=C1C[C@@](C)(C(C)(C)C)CC(=C)/C1=C/c1cc(C)c(Cl)c(C[C@H](C)Cl)c1")

    Chem.SetUseLegacyStereoPerception(True)

  def testValidationMrv(self):
    Chem.SetUseLegacyStereoPerception(False)
    mrvBlock = ""
    fileN = os.path.join(RDConfig.RDBaseDir, 'Code', 'GraphMol', 'Wrap', 'test_data',
                         'DoubleBond.mrv')
    with open(fileN) as f:
      mrvBlock = f.read()
    mol = Chem.MolFromMrvBlock(mrvBlock, True, True)

    smi = Chem.MolToSmiles(mol)
    self.assertEqual(smi, "C=C1CC(C)(C(C)(C)C)CC(=C)C1=Cc1cc(C)c(Cl)c(C[C@H](C)Cl)c1")

    mol = Chem.MolFromMrvFile(fileN, True, True)
    smi = Chem.MolToSmiles(mol)
    self.assertEqual(smi, "C=C1CC(C)(C(C)(C)C)CC(=C)C1=Cc1cc(C)c(Cl)c(C[C@H](C)Cl)c1")

    Chem.SetUseLegacyStereoPerception(True)

  def test_picklingWithAddedAttribs(self):
    m = Chem.MolFromSmiles("C")
    m.foo = 1
    m.SetIntProp("bar", 2)
    pkl = pickle.dumps(m)
    nm = pickle.loads(pkl)
    self.assertEqual(nm.GetIntProp("bar"), 2)
    self.assertEqual(nm.foo, 1)

  def testGithubIssue6306(self):
    # test of unpickling
    props = Chem.GetDefaultPickleProperties()
    try:
      Chem.SetDefaultPickleProperties(Chem.PropertyPickleOptions.AllProps)
      mols = [Chem.MolFromSmiles(s) for s in ["C", "CC"]]
      scaffolds = [MurckoScaffold.GetScaffoldForMol(m) for m in mols]
      # this shouldn't throw an exception
      unpickler = [pickle.loads(pickle.dumps(m)) for m in mols]
    finally:
      Chem.SetDefaultPickleProperties(props)

  @unittest.skipIf(not hasattr(Chem, 'MaeWriter'), "not build with MAEParser support")
  def testMaeWriter(self):
    mol = Chem.MolFromSmiles("C1CCCCC1")
    self.assertTrue(mol)
    title = "random test mol"
    mol.SetProp('_Name', title)

    ofile = os.path.join(RDConfig.RDBaseDir, 'Code', 'GraphMol', 'Wrap', 'test_data',
                         'outMaeWriter.mae')
    writer1 = Chem.MaeWriter(ofile)

    osio = StringIO()
    writer2 = Chem.MaeWriter(osio)

    for writer in (writer1, writer2):
      writer.write(mol)
      writer.close()
      del writer

    with open(ofile) as f:
      maefile = f.read()

    self.assertEqual(maefile, osio.getvalue())

    self.assertIn('s_m_m2io_version', maefile)

    self.assertIn('f_m_ct', maefile)

    self.assertIn('s_m_title', maefile)
    self.assertIn(title, maefile)

    self.assertIn(f' m_atom[{mol.GetNumAtoms()}] {{', maefile)

    self.assertEqual(maefile.count("A0A0A0"), 6)  # 6 grey-colored heavy atoms

    self.assertTrue(f' m_bond[{mol.GetNumBonds()}] {{', maefile)

  @unittest.skipIf(not hasattr(Chem, 'MaeWriter'), "not build with MAEParser support")
  def testMaeWriterProps(self):
    mol = Chem.MolFromSmiles("C1CCCCC1")
    self.assertTrue(mol)

    title = "random test mol"
    mol.SetProp('_Name', title)

    boolProp = False
    intProp = 123454321
    realProp = 2.718282  # Mae files have a predefined precision of 6 digits!
    strProp = r"This is a dummy prop, yay!"

    ignored_prop = 'ignored_prop'
    str_dummy_prop = 'str_dummy_prop'
    mol_prop = 'mol_prop'
    atom_prop = 'atom_prop'
    bond_prop = 'bond_prop'
    exported_props = [str_dummy_prop, mol_prop, atom_prop, bond_prop]

    mol.SetIntProp(mol_prop, intProp)
    mol.SetProp(str_dummy_prop, strProp)
    mol.SetProp(ignored_prop, ignored_prop)

    atomIdx = 2
    at = mol.GetAtomWithIdx(atomIdx)
    at.SetDoubleProp(atom_prop, realProp)
    at.SetProp(str_dummy_prop, strProp)
    at.SetProp(ignored_prop, ignored_prop)

    bondIdx = 4
    b = mol.GetBondWithIdx(bondIdx)
    b.SetBoolProp(bond_prop, boolProp)
    b.SetProp(str_dummy_prop, strProp)
    b.SetProp(ignored_prop, ignored_prop)

    heavyAtomColor = "767676"

    osio = StringIO()
    with Chem.MaeWriter(osio) as w:
      w.SetProps(exported_props)
      w.write(mol, heavyAtomColor=heavyAtomColor)

    maestr = osio.getvalue()

    ctBlockStart = maestr.find('f_m_ct')
    atomBlockStart = maestr.find(' m_atom[')
    bondBlockStart = maestr.find(' m_bond[')

    self.assertNotEqual(ctBlockStart, -1)
    self.assertNotEqual(atomBlockStart, -1)
    self.assertNotEqual(bondBlockStart, -1)

    self.assertGreater(bondBlockStart, atomBlockStart)
    self.assertGreater(atomBlockStart, ctBlockStart)

    # structure properties
    self.assertIn(mol_prop, maestr[ctBlockStart:atomBlockStart])
    self.assertIn(str(intProp), maestr[ctBlockStart:atomBlockStart])

    self.assertIn(str_dummy_prop, maestr[ctBlockStart:atomBlockStart])
    self.assertIn(strProp, maestr[ctBlockStart:atomBlockStart])

    self.assertNotIn(ignored_prop, maestr[ctBlockStart:atomBlockStart])

    # atom properties
    self.assertIn(atom_prop, maestr[atomBlockStart:bondBlockStart])
    self.assertIn(str_dummy_prop, maestr[atomBlockStart:bondBlockStart])

    self.assertNotIn(ignored_prop, maestr[atomBlockStart:bondBlockStart])

    for line in maestr[atomBlockStart:bondBlockStart].split('\n'):
      if line.strip().startswith(str(atomIdx + 1)):
        break
    self.assertIn(str(realProp), line)
    self.assertIn(strProp, line)
    self.assertIn(heavyAtomColor, line)

    # bond properties
    self.assertIn(bond_prop, maestr[bondBlockStart:])
    self.assertIn(str_dummy_prop, maestr[bondBlockStart:])

    self.assertNotIn(ignored_prop, maestr[bondBlockStart:])

    for line in maestr[bondBlockStart:].split('\n'):
      if line.strip().startswith(str(bondIdx + 1)):
        break
    self.assertIn(str(int(boolProp)), line)
    self.assertIn(strProp, line)

  @unittest.skipIf(not hasattr(Chem, 'MaeWriter'), "not build with MAEParser support")
  def testMaeWriterRoundtrip(self):
    smiles = "C1CCCCC1"
    mol = Chem.MolFromSmiles(smiles)
    self.assertTrue(mol)

    osio = StringIO()
    with Chem.MaeWriter(osio) as w:
      w.write(mol)

    isio = BytesIO(osio.getvalue().encode())
    with Chem.MaeMolSupplier(isio) as r:
      roundtrip_mol = next(r)
    self.assertTrue(roundtrip_mol)

    self.assertEqual(Chem.MolToSmiles(roundtrip_mol), smiles)

  @unittest.skipIf(not hasattr(Chem, 'MaeWriter'), "not build with MAEParser support")
  def testMaeWriterGetText(self):
    smiles = "C1CCCCC1"
    mol = Chem.MolFromSmiles(smiles)
    self.assertTrue(mol)

    dummy_prop = 'dummy_prop'
    another_dummy_prop = 'another_dummy_prop'
    mol.SetProp(dummy_prop, dummy_prop)
    mol.SetProp(another_dummy_prop, another_dummy_prop)

    heavyAtomColor = "767676"

    osio = StringIO()
    with Chem.MaeWriter(osio) as w:
      w.SetProps([dummy_prop])
      w.write(mol, heavyAtomColor=heavyAtomColor)

    iomae = osio.getvalue()

    ctBlockStart = iomae.find('f_m_ct')
    self.assertNotEqual(ctBlockStart, -1)

    self.assertIn(dummy_prop, iomae)
    self.assertNotIn(another_dummy_prop, iomae)

    mae = Chem.MaeWriter.GetText(mol, heavyAtomColor, -1, [dummy_prop])

    self.assertEqual(mae, iomae[ctBlockStart:])

  def test3dChiralMolFile(self):
    fileN = os.path.join(RDConfig.RDBaseDir, 'Code', 'GraphMol', 'FileParsers', 'test_data',
                         'Cubane.sdf')
    with open(fileN, 'r') as inF:
      inD = inF.read()

    fileWedges = os.path.join(RDConfig.RDBaseDir, 'Code', 'GraphMol', 'FileParsers', 'test_data',
                              'CubaneWedges.cxsmi')
    with open(fileWedges, 'r') as inF:
      inWedges = inF.read()

    fileNoWedges = os.path.join(RDConfig.RDBaseDir, 'Code', 'GraphMol', 'FileParsers', 'test_data',
                                'CubaneNoWedges.cxsmi')
    with open(fileNoWedges, 'r') as inF:
      inNoWedges = inF.read()

    m1 = Chem.MolFromMolBlock(inD, sanitize=False, removeHs=False, strictParsing=True)
    self.assertTrue(m1 is not None)
    self.assertTrue(m1.GetNumAtoms() == 16)
    smi = Chem.MolToCXSmiles(m1)
    self.assertTrue(smi == inWedges)

    m1 = Chem.MolFromMolFile(fileN, sanitize=False, removeHs=False, strictParsing=True)
    self.assertTrue(m1 is not None)
    self.assertTrue(m1.GetNumAtoms() == 16)
    smi = Chem.MolToCXSmiles(m1)
    self.assertTrue(smi == inWedges)

    m1 = Chem.MolFromMolBlock(inD, sanitize=False, removeHs=False, strictParsing=True)
    Chem.RemoveNonExplicit3DChirality(m1)

    self.assertTrue(m1 is not None)
    self.assertTrue(m1.GetNumAtoms() == 16)
    smi = Chem.MolToCXSmiles(m1)
    self.assertTrue(smi == inNoWedges)

    m1 = Chem.MolFromMolFile(fileN, sanitize=False, removeHs=False, strictParsing=True)
    Chem.RemoveNonExplicit3DChirality(m1)

    self.assertTrue(m1 is not None)
    self.assertTrue(m1.GetNumAtoms() == 16)
    smi = Chem.MolToCXSmiles(m1)
    self.assertTrue(smi == inNoWedges)

  def test3dChiralMrvFile(self):
    fileN = os.path.join(RDConfig.RDBaseDir, 'Code', 'GraphMol', 'MarvinParse', 'test_data',
                         'Cubane.mrv')
    with open(fileN, 'r') as inF:
      inD = inF.read()

    fileWedges = os.path.join(RDConfig.RDBaseDir, 'Code', 'GraphMol', 'MarvinParse', 'test_data',
                              'CubaneWedges.cxsmi')
    with open(fileWedges, 'r') as inF:
      inWedges = inF.read()

    fileNoWedges = os.path.join(RDConfig.RDBaseDir, 'Code', 'GraphMol', 'MarvinParse', 'test_data',
                                'CubaneNoWedges.cxsmi')
    with open(fileNoWedges, 'r') as inF:
      inNoWedges = inF.read()

    m1 = Chem.MolFromMrvBlock(inD, sanitize=False, removeHs=False)

    self.assertTrue(m1 is not None)
    self.assertTrue(m1.GetNumAtoms() == 16)
    smi = Chem.MolToCXSmiles(m1)
    sys.stdout.flush()
    self.assertTrue(smi == inWedges)

    m1 = Chem.MolFromMrvFile(fileN, sanitize=False, removeHs=False)

    self.assertTrue(m1 is not None)
    self.assertTrue(m1.GetNumAtoms() == 16)
    smi = Chem.MolToCXSmiles(m1)
    self.assertTrue(smi == inWedges)

    m1 = Chem.MolFromMrvBlock(inD, sanitize=False, removeHs=False)
    Chem.RemoveNonExplicit3DChirality(m1)

    self.assertTrue(m1 is not None)
    self.assertTrue(m1.GetNumAtoms() == 16)
    smi = Chem.MolToCXSmiles(m1)
    self.assertTrue(smi == inNoWedges)

    m1 = Chem.MolFromMrvFile(fileN, sanitize=False, removeHs=False)
    Chem.RemoveNonExplicit3DChirality(m1)

    self.assertTrue(m1 is not None)
    self.assertTrue(m1.GetNumAtoms() == 16)
    smi = Chem.MolToCXSmiles(m1)
    self.assertTrue(smi == inNoWedges)

  def test3dChiralCxsmiles(self):
    fileN = os.path.join(RDConfig.RDBaseDir, 'Code', 'GraphMol', 'SmilesParse', 'test_data',
                         'Cubane.cxsmi')
    with open(fileN, 'r') as inF:
      inD = inF.read()

    fileWedges = os.path.join(RDConfig.RDBaseDir, 'Code', 'GraphMol', 'SmilesParse', 'test_data',
                              'Cubane.cxsmi.expected3D.cxsmi')
    with open(fileWedges, 'r') as inF:
      inWedges = inF.read()

    fileNoWedges = os.path.join(RDConfig.RDBaseDir, 'Code', 'GraphMol', 'SmilesParse', 'test_data',
                                'Cubane.cxsmi.expected3D2.cxsmi')
    with open(fileNoWedges, 'r') as inF:
      inNoWedges = inF.read()

    ps = Chem.SmilesParserParams()
    ps.allowCXSMILES = True
    ps.parseName = False
    ps.sanitize = False
    ps.removeHs = False

    m1 = Chem.MolFromSmiles(inD, ps)
    self.assertTrue(m1 is not None)
    self.assertTrue(m1.GetNumAtoms() == 16)
    smi = Chem.MolToCXSmiles(m1)
    sys.stdout.flush()
    self.assertTrue(smi == inWedges)

    m1 = Chem.MolFromSmiles(inD, ps)
    Chem.RemoveNonExplicit3DChirality(m1)

    self.assertTrue(m1 is not None)
    self.assertTrue(m1.GetNumAtoms() == 16)
    smi = Chem.MolToCXSmiles(m1)
    print('inWedges: ', inNoWedges)

    self.assertTrue(smi == inNoWedges)

  def testReapplyMolBlockWedging(self):
    fileN = os.path.join(RDConfig.RDBaseDir, 'Code', 'GraphMol', 'MarvinParse', 'test_data',
                         'JDQ443_atrop1.mrv')

    fileReapplied = os.path.join(RDConfig.RDBaseDir, 'Code', 'GraphMol', 'MarvinParse', 'test_data',
                                 'JDQ443_atrop1.mrv.expected.sdf')
    with open(fileReapplied, 'r') as inF:
      isReapplied = inF.read()

    fileNotReapplied = os.path.join(RDConfig.RDBaseDir, 'Code', 'GraphMol', 'MarvinParse',
                                    'test_data', 'JDQ443_atrop1.mrv.expected2.sdf')
    with open(fileNotReapplied, 'r') as inF:
      isNotReapplied = inF.read()

    m = Chem.MolFromMrvFile(fileN, False, False)
    self.assertTrue(m is not None)
    self.assertTrue(m.GetNumAtoms() == 38)
    mBlock = Chem.MolToMolBlock(m, False, -1, True, True)

    sys.stdout.flush()
    self.assertTrue(mBlock == isNotReapplied)
    Chem.ReapplyMolBlockWedging(m)

    mBlock = Chem.MolToMolBlock(m, False, -1, True, True)
    sys.stdout.flush()

    self.assertTrue(mBlock == isReapplied)

  def testReapplyMolBlockWedgingAllBondTypes(self):
    m = Chem.MolFromMolBlock('''
  Mrv2311 04232413302D          

  0  0  0     0  0            999 V3000
M  V30 BEGIN CTAB
M  V30 COUNTS 5 4 0 0 0
M  V30 BEGIN ATOM
M  V30 1 S -11.583 11.3533 0 0
M  V30 2 C -12.9167 10.5833 0 0
M  V30 3 O -11.583 12.8933 0 0
M  V30 4 C -10.2493 10.5833 0 0
M  V30 5 C -10.2493 9.0433 0 0
M  V30 END ATOM
M  V30 BEGIN BOND
M  V30 1 1 4 5
M  V30 2 1 2 1
M  V30 3 1 1 4
M  V30 4 2 1 3 CFG=1
M  V30 END BOND
M  V30 END CTAB
M  END
''')
    self.assertEqual(m.GetBondWithIdx(3).GetBondType(), Chem.BondType.DOUBLE)
    Chem.ReapplyMolBlockWedging(m)
    self.assertEqual(m.GetBondWithIdx(3).GetBondDir(), Chem.BondDir.BEGINWEDGE)
    Chem.ReapplyMolBlockWedging(m, False)
    self.assertEqual(m.GetBondWithIdx(3).GetBondDir(), Chem.BondDir.NONE)

  def testAtropisomerWedging(self):
    m = Chem.MolFromSmiles(
      'CC1=C(N2C=CC=C2[C@H](C)Cl)C(C)CCC1 |(2.679,0.4142,;1.3509,1.181,;0.0229,0.4141,;0.0229,-1.1195,;1.2645,-2.0302,;0.7901,-3.4813,;-0.7446,-3.4813,;-1.219,-2.0302,;-2.679,-1.5609,;-3.0039,-0.0556,;-3.8202,-2.595,;-1.3054,1.1809,;-2.6335,0.4141,;-1.3054,2.7145,;0.0229,3.4813,;1.3509,2.7146,),wD:2.11,wU:8.10,&1:8|'
    )
    self.assertTrue(m is not None)
    self.assertTrue(m.GetNumAtoms() == 16)

    sys.stdout.flush()
    flags = Chem.CXSmilesFields.CX_COORDS | \
                        Chem.CXSmilesFields.CX_MOLFILE_VALUES | \
                        Chem.CXSmilesFields.CX_ATOM_PROPS | \
                        Chem.CXSmilesFields.CX_BOND_CFG | \
                        Chem.CXSmilesFields.CX_ENHANCEDSTEREO

    ps = Chem.SmilesWriteParams()
    ps.canonical = True

    smi = Chem.MolToCXSmiles(m, ps, flags, Chem.RestoreBondDirOption.RestoreBondDirOptionTrue)
    self.assertTrue(smi == 'CC1=C(n2cccc2[C@H](C)Cl)C(C)CCC1 |(2.679,0.4142,;1.3509,1.181,;0.0229,0.4141,;0.0229,-1.1195,;1.2645,-2.0302,;0.7901,-3.4813,;-0.7446,-3.4813,;-1.219,-2.0302,;-2.679,-1.5609,;-3.0039,-0.0556,;-3.8202,-2.595,;-1.3054,1.1809,;-2.6335,0.4141,;-1.3054,2.7145,;0.0229,3.4813,;1.3509,2.7146,),wD:2.11,wU:8.10,&1:8|')

    flags = Chem.CXSmilesFields.CX_COORDS | \
                        Chem.CXSmilesFields.CX_MOLFILE_VALUES | \
                        Chem.CXSmilesFields.CX_ATOM_PROPS | \
                        Chem.CXSmilesFields.CX_BOND_ATROPISOMER | \
                        Chem.CXSmilesFields.CX_ENHANCEDSTEREO

    smi = Chem.MolToCXSmiles(m, ps, flags, Chem.RestoreBondDirOption.RestoreBondDirOptionTrue)

    self.assertTrue(
      smi ==
      'CC1=C(n2cccc2[C@H](C)Cl)C(C)CCC1 |(2.679,0.4142,;1.3509,1.181,;0.0229,0.4141,;0.0229,-1.1195,;1.2645,-2.0302,;0.7901,-3.4813,;-0.7446,-3.4813,;-1.219,-2.0302,;-2.679,-1.5609,;-3.0039,-0.0556,;-3.8202,-2.595,;-1.3054,1.1809,;-2.6335,0.4141,;-1.3054,2.7145,;0.0229,3.4813,;1.3509,2.7146,),wD:2.11,&1:8|')
    flags = Chem.CXSmilesFields.CX_COORDS | \
                        Chem.CXSmilesFields.CX_MOLFILE_VALUES | \
                        Chem.CXSmilesFields.CX_ATOM_PROPS | \
                        Chem.CXSmilesFields.CX_ENHANCEDSTEREO

    smi = Chem.MolToCXSmiles(m, ps, flags, Chem.RestoreBondDirOption.RestoreBondDirOptionTrue)
    self.assertTrue(
      smi ==
      'CC1=C(n2cccc2[C@H](C)Cl)C(C)CCC1 |(2.679,0.4142,;1.3509,1.181,;0.0229,0.4141,;0.0229,-1.1195,;1.2645,-2.0302,;0.7901,-3.4813,;-0.7446,-3.4813,;-1.219,-2.0302,;-2.679,-1.5609,;-3.0039,-0.0556,;-3.8202,-2.595,;-1.3054,1.1809,;-2.6335,0.4141,;-1.3054,2.7145,;0.0229,3.4813,;1.3509,2.7146,),&1:8|'
    )

  def testAtropisomerWedgingRigorousEnhancedStereo(self):
    m = Chem.MolFromSmiles(
      'CC1=C(N2C=CC=C2[C@H](C)Cl)C(C)CCC1 |(2.679,0.4142,;1.3509,1.181,;0.0229,0.4141,;0.0229,-1.1195,;1.2645,-2.0302,;0.7901,-3.4813,;-0.7446,-3.4813,;-1.219,-2.0302,;-2.679,-1.5609,;-3.0039,-0.0556,;-3.8202,-2.595,;-1.3054,1.1809,;-2.6335,0.4141,;-1.3054,2.7145,;0.0229,3.4813,;1.3509,2.7146,),wD:2.11,wU:8.10,&1:8|'
    )
    self.assertTrue(m is not None)
    self.assertTrue(m.GetNumAtoms() == 16)

    sys.stdout.flush()
    flags = Chem.CXSmilesFields.CX_COORDS | \
                        Chem.CXSmilesFields.CX_MOLFILE_VALUES | \
                        Chem.CXSmilesFields.CX_ATOM_PROPS | \
                        Chem.CXSmilesFields.CX_BOND_CFG | \
                        Chem.CXSmilesFields.CX_ENHANCEDSTEREO

    ps = Chem.SmilesWriteParams()
    ps.canonical = True

    m = Chem.CanonicalizeStereoGroups(m)
    smi = Chem.MolToCXSmiles(m, ps, flags, Chem.RestoreBondDirOption.RestoreBondDirOptionTrue)
    print('smi: ', smi)
    self.assertTrue(smi == 'CC1=C(n2cccc2[C@H](C)Cl)C(C)CCC1 |(2.679,0.4142,;1.3509,1.181,;0.0229,0.4141,;0.0229,-1.1195,;1.2645,-2.0302,;0.7901,-3.4813,;-0.7446,-3.4813,;-1.219,-2.0302,;-2.679,-1.5609,;-3.0039,-0.0556,;-3.8202,-2.595,;-1.3054,1.1809,;-2.6335,0.4141,;-1.3054,2.7145,;0.0229,3.4813,;1.3509,2.7146,),wD:8.9,2.11,a:2,&1:8|')

  def test_picklingWithAddedAttribs(self):
    m = Chem.MolFromSmiles("C")
    m.foo = 1
    m.SetIntProp("bar", 2)
    pkl = pickle.dumps(m)
    nm = pickle.loads(pkl)
    self.assertEqual(nm.GetIntProp("bar"), 2)
    self.assertEqual(nm.foo, 1)

  def testGithubIssue6306(self):
    # test of unpickling
    props = Chem.GetDefaultPickleProperties()
    try:
      Chem.SetDefaultPickleProperties(Chem.PropertyPickleOptions.AllProps)
      mols = [Chem.MolFromSmiles(s) for s in ["C", "CC"]]
      scaffolds = [MurckoScaffold.GetScaffoldForMol(m) for m in mols]
      # this shouldn't throw an exception
      unpickler = [pickle.loads(pickle.dumps(m)) for m in mols]
    finally:
      Chem.SetDefaultPickleProperties(props)

  @unittest.skipIf(not hasattr(Chem, 'MaeWriter'), "not built with MAEParser support")
  def testMaeWriter(self):
    mol = Chem.MolFromSmiles("C1CCCCC1")
    self.assertTrue(mol)
    title = "random test mol"
    mol.SetProp('_Name', title)

    ofile = os.path.join(RDConfig.RDBaseDir, 'Code', 'GraphMol', 'Wrap', 'test_data',
                         'outMaeWriter.mae')
    writer1 = Chem.MaeWriter(ofile)

    osio = StringIO()
    writer2 = Chem.MaeWriter(osio)

    for writer in (writer1, writer2):
      writer.write(mol)
      writer.close()
      del writer

    with open(ofile) as f:
      maefile = f.read()

    self.assertEqual(maefile, osio.getvalue())

    self.assertIn('s_m_m2io_version', maefile)

    self.assertIn('f_m_ct', maefile)

    self.assertIn('s_m_title', maefile)
    self.assertIn(title, maefile)

    self.assertIn(f' m_atom[{mol.GetNumAtoms()}] {{', maefile)

    self.assertTrue(f' m_bond[{mol.GetNumBonds()}] {{', maefile)

  @unittest.skipIf(not hasattr(Chem, 'MaeWriter'), "not built with MAEParser support")
  def testMaeWriterProps(self):
    mol = Chem.MolFromSmiles("C1CCCCC1")
    self.assertTrue(mol)

    title = "random test mol"
    mol.SetProp('_Name', title)

    boolProp = False
    intProp = 123454321
    realProp = 2.718282  # Mae files have a predefined precision of 6 digits!
    strProp = r"This is a dummy prop, yay!"

    ignored_prop = 'ignored_prop'
    str_dummy_prop = 'str_dummy_prop'
    mol_prop = 'mol_prop'
    atom_prop = 'atom_prop'
    bond_prop = 'bond_prop'
    exported_props = [str_dummy_prop, mol_prop, atom_prop, bond_prop]

    mol.SetIntProp(mol_prop, intProp)
    mol.SetProp(str_dummy_prop, strProp)
    mol.SetProp(ignored_prop, ignored_prop)

    atomIdx = 2
    at = mol.GetAtomWithIdx(atomIdx)
    at.SetDoubleProp(atom_prop, realProp)
    at.SetProp(str_dummy_prop, strProp)
    at.SetProp(ignored_prop, ignored_prop)

    bondIdx = 4
    b = mol.GetBondWithIdx(bondIdx)
    b.SetBoolProp(bond_prop, boolProp)
    b.SetProp(str_dummy_prop, strProp)
    b.SetProp(ignored_prop, ignored_prop)

    osio = StringIO()
    with Chem.MaeWriter(osio) as w:
      w.SetProps(exported_props)
      w.write(mol)

    maestr = osio.getvalue()

    ctBlockStart = maestr.find('f_m_ct')
    atomBlockStart = maestr.find(' m_atom[')
    bondBlockStart = maestr.find(' m_bond[')

    self.assertNotEqual(ctBlockStart, -1)
    self.assertNotEqual(atomBlockStart, -1)
    self.assertNotEqual(bondBlockStart, -1)

    self.assertGreater(bondBlockStart, atomBlockStart)
    self.assertGreater(atomBlockStart, ctBlockStart)

    # structure properties
    self.assertIn(mol_prop, maestr[ctBlockStart:atomBlockStart])
    self.assertIn(str(intProp), maestr[ctBlockStart:atomBlockStart])

    self.assertIn(str_dummy_prop, maestr[ctBlockStart:atomBlockStart])
    self.assertIn(strProp, maestr[ctBlockStart:atomBlockStart])

    self.assertNotIn(ignored_prop, maestr[ctBlockStart:atomBlockStart])

    # atom properties
    self.assertIn(atom_prop, maestr[atomBlockStart:bondBlockStart])
    self.assertIn(str_dummy_prop, maestr[atomBlockStart:bondBlockStart])

    self.assertNotIn(ignored_prop, maestr[atomBlockStart:bondBlockStart])

    for line in maestr[atomBlockStart:bondBlockStart].split('\n'):
      if line.strip().startswith(str(atomIdx + 1)):
        break
    self.assertIn(str(realProp), line)
    self.assertIn(strProp, line)

    # bond properties
    self.assertIn(bond_prop, maestr[bondBlockStart:])
    self.assertIn(str_dummy_prop, maestr[bondBlockStart:])

    self.assertNotIn(ignored_prop, maestr[bondBlockStart:])

    for line in maestr[bondBlockStart:].split('\n'):
      if line.strip().startswith(str(bondIdx + 1)):
        break
    self.assertIn(str(int(boolProp)), line)
    self.assertIn(strProp, line)

  @unittest.skipIf(not hasattr(Chem, 'MaeWriter'), "not built with MAEParser support")
  def testMaeWriterRoundtrip(self):
    smiles = "C1CCCCC1"
    mol = Chem.MolFromSmiles(smiles)
    self.assertTrue(mol)

    osio = StringIO()
    with Chem.MaeWriter(osio) as w:
      w.write(mol)

    isio = BytesIO(osio.getvalue().encode())
    with Chem.MaeMolSupplier(isio) as r:
      roundtrip_mol = next(r)
    self.assertTrue(roundtrip_mol)

    self.assertEqual(Chem.MolToSmiles(roundtrip_mol), smiles)

  @unittest.skipIf(not hasattr(Chem, 'MaeWriter'), "not built with MAEParser support")
  def testMaeWriterGetText(self):
    smiles = "C1CCCCC1"
    mol = Chem.MolFromSmiles(smiles)
    self.assertTrue(mol)

    dummy_prop = 'dummy_prop'
    another_dummy_prop = 'another_dummy_prop'
    mol.SetProp(dummy_prop, dummy_prop)
    mol.SetProp(another_dummy_prop, another_dummy_prop)

    osio = StringIO()
    with Chem.MaeWriter(osio) as w:
      w.SetProps([dummy_prop])
      w.write(mol)

    iomae = osio.getvalue()

    ctBlockStart = iomae.find('f_m_ct')
    self.assertNotEqual(ctBlockStart, -1)

    self.assertIn(dummy_prop, iomae)
    self.assertNotIn(another_dummy_prop, iomae)

    mae = Chem.MaeWriter.GetText(mol, -1, [dummy_prop])

    self.assertEqual(mae, iomae[ctBlockStart:])

  def test_HapticBondsToDative(self):
    fefile = os.path.join(RDConfig.RDBaseDir, 'Code', 'GraphMol', 'MolStandardize', 'test_data',
                          'ferrocene.mol')
    femol = Chem.MolFromMolFile(fefile)
    newfemol = Chem.rdmolops.HapticBondsToDative(femol)
    self.assertEqual(Chem.MolToSmiles(newfemol),
                     'c12->[Fe+2]3456789(<-c1c->3[cH-]->4c->52)<-c1c->6c->7[cH-]->8c->91')

  def test_DativeBondsToHaptic(self):
    fefile = os.path.join(RDConfig.RDBaseDir, 'Code', 'GraphMol', 'MolStandardize', 'test_data',
                          'ferrocene.mol')
    femol = Chem.MolFromMolFile(fefile)
    newfemol = Chem.rdmolops.HapticBondsToDative(femol)
    backfemol = Chem.rdmolops.DativeBondsToHaptic(newfemol)
    self.assertEqual(Chem.MolToSmiles(femol), Chem.MolToSmiles(backfemol))

  def testTranslateChiralFlag(self):
    mol = Chem.MolFromSmiles("C[C@@](N)(F)C[C@](C)(O)F |a:1|")
    flagMol = Chem.Mol(mol)
    flagMol.SetIntProp("_MolFileChiralFlag", 1)
    Chem.TranslateChiralFlagToStereoGroups(flagMol)
    sgs = flagMol.GetStereoGroups()
    self.assertEqual(len(sgs), 1)
    self.assertEqual(len(sgs[0].GetAtoms()), 2)
    self.assertEqual(sgs[0].GetGroupType(), Chem.StereoGroupType.STEREO_ABSOLUTE)

    flagMol = Chem.Mol(mol)
    flagMol.SetIntProp("_MolFileChiralFlag", 0)
    Chem.TranslateChiralFlagToStereoGroups(flagMol)
    sgs = flagMol.GetStereoGroups()
    self.assertEqual(len(sgs), 2)
    self.assertEqual(sgs[0].GetGroupType(), Chem.StereoGroupType.STEREO_ABSOLUTE)
    self.assertEqual(len(sgs[0].GetAtoms()), 1)

    self.assertEqual(sgs[1].GetGroupType(), Chem.StereoGroupType.STEREO_AND)
    self.assertEqual(len(sgs[1].GetAtoms()), 1)

    flagMol = Chem.Mol(mol)
    flagMol.SetIntProp("_MolFileChiralFlag", 0)
    Chem.TranslateChiralFlagToStereoGroups(flagMol, Chem.StereoGroupType.STEREO_OR)
    sgs = flagMol.GetStereoGroups()
    self.assertEqual(len(sgs), 2)
    self.assertEqual(sgs[0].GetGroupType(), Chem.StereoGroupType.STEREO_ABSOLUTE)
    self.assertEqual(len(sgs[0].GetAtoms()), 1)

    self.assertEqual(sgs[1].GetGroupType(), Chem.StereoGroupType.STEREO_OR)
    self.assertEqual(len(sgs[1].GetAtoms()), 1)

  def testHasQueryHs(self):
    for sma, hasQHs in [
      ("[#1]", (True, False)), ("[#1,N]", (True, True)), ("[$(C-[H])]", (True, False)),
      ("[$([C,#1])]", (True, True)),
      ("[$(c([C;!R;!$(C-[N,O,S]);!$(C-[H])](=O))1naaaa1),$(c([C;!R;!$(C-[N,O,S]);!$(C-[H])](=O))1naa[n,s,o]1)]",
       (True, False))
    ]:
      pat = Chem.MolFromSmarts(sma)
      self.assertEqual(Chem.HasQueryHs(pat), hasQHs)

  def testMolHasQuery(self):
    m1 = Chem.MolFromSmiles("CCO")
    self.assertFalse(m1.HasQuery())

    m2 = Chem.MolFromSmarts("[#6][#6][#8]")
    self.assertTrue(m2.HasQuery())

    m3 = Chem.MolFromSmiles("CC~O")
    self.assertTrue(m3.HasQuery())

  def testMrvHandling(self):
    fn1 = os.path.join(RDConfig.RDBaseDir, 'Code', 'GraphMol', 'MarvinParse', 'test_data',
                       'aspirin.mrv')
    mol = Chem.MolFromMrvFile(fn1)
    self.assertIsNotNone(mol)
    self.assertEqual(mol.GetNumAtoms(), 13)
    mrv = Chem.MolToMrvBlock(mol)
    self.assertTrue('<molecule molID="m1">' in mrv)
    self.assertFalse('<reaction>' in mrv)

    fName = tempfile.NamedTemporaryFile(suffix='.mrv').name
    self.assertFalse(os.path.exists(fName))
    Chem.MolToMrvFile(mol, fName)
    self.assertTrue(os.path.exists(fName))
    os.unlink(fName)

    with open(fn1, 'r') as inf:
      ind = inf.read()
    mol = Chem.MolFromMrvBlock(ind)
    self.assertIsNotNone(mol)
    self.assertEqual(mol.GetNumAtoms(), 13)

  def testAtomMapsInCanonicalization(self):
    mol = Chem.MolFromSmiles("[F:1]C([F:2])O")
    ranks = Chem.CanonicalRankAtoms(mol, breakTies=False, includeAtomMaps=True)
    self.assertNotEqual(ranks[0], ranks[2])
    ranks = Chem.CanonicalRankAtoms(mol, breakTies=False, includeAtomMaps=False)
    self.assertEqual(ranks[0], ranks[2])

  def testExpandAndCollapseAttachmentPoints(self):
    mol = Chem.MolFromSmarts("*CO")
    self.assertEqual(mol.GetNumAtoms(), 3)
    mol.GetAtomWithIdx(2).SetIntProp("molAttchpt", 1)
    Chem.ExpandAttachmentPoints(mol)
    self.assertEqual(mol.GetNumAtoms(), 4)
    self.assertTrue(mol.GetAtomWithIdx(3).HasQuery())

    Chem.CollapseAttachmentPoints(mol)
    self.assertEqual(mol.GetNumAtoms(), 3)

    Chem.ExpandAttachmentPoints(mol, addAsQueries=False)
    self.assertEqual(mol.GetNumAtoms(), 4)
    self.assertFalse(mol.GetAtomWithIdx(3).HasQuery())
    Chem.CollapseAttachmentPoints(mol, markedOnly=False)
    self.assertEqual(mol.GetNumAtoms(), 2)

  def testAddStereoAnnotations(self):
    mol = Chem.MolFromSmiles(
      "C[C@@H]1N[C@H](C)[C@@H]([C@H](C)[C@@H]1C)C1[C@@H](C)O[C@@H](C)[C@@H](C)[C@H]1C/C=C/C |a:5,o1:1,8,o2:14,16,&1:11,18,&2:3,6,r|"
    )
    self.assertIsNotNone(mol)
    Chem.rdCIPLabeler.AssignCIPLabels(mol)
    Chem.AddStereoAnnotations(mol)
    self.assertEqual(mol.GetAtomWithIdx(5).GetProp("atomNote"), "abs (S)")
    self.assertEqual(mol.GetAtomWithIdx(3).GetProp("atomNote"), "and2")

  def testIsRingFused(self):
    molOrig = Chem.MolFromSmiles("C1C(C2CC3CCCCC3C12)C1CCCCC1")
    mol = Chem.RWMol(molOrig)
    ri = mol.GetRingInfo()
    self.assertEqual(ri.NumRings(), 4)
    fusedRings = [ri.IsRingFused(i) for i in range(ri.NumRings())]
    self.assertEqual(fusedRings.count(True), 3)
    self.assertEqual(fusedRings.count(False), 1)
    atoms = mol.GetSubstructMatch(Chem.MolFromSmarts("[$(C1CCC1)]-@[$(C1CCCCC1)]"))
    mol.RemoveBond(*atoms)
    Chem.SanitizeMol(mol)
    self.assertEqual(Chem.MolToSmiles(mol), "C1CCC(CC2CCC2C2CCCCC2)CC1")
    self.assertEqual(ri.NumRings(), 3)
    fusedRings = [ri.IsRingFused(i) for i in range(ri.NumRings())]
    self.assertEqual(fusedRings.count(True), 0)
    self.assertEqual(fusedRings.count(False), 3)
    mol = Chem.RWMol(molOrig)
    ri = mol.GetRingInfo()
    self.assertEqual(ri.NumRings(), 4)
    fusedRings = [ri.IsRingFused(i) for i in range(ri.NumRings())]
    self.assertEqual(fusedRings.count(True), 3)
    self.assertEqual(fusedRings.count(False), 1)
    fusedBonds = [ri.NumFusedBonds(i) for i in range(ri.NumRings())]
    self.assertEqual(fusedBonds.count(0), 1)
    self.assertEqual(fusedBonds.count(1), 2)
    self.assertEqual(fusedBonds.count(2), 1)
    atoms = mol.GetSubstructMatch(
      Chem.MolFromSmarts("[$(C1CCCCC1-!@[CX4;R1;r4])].[$(C1C(-!@[CX4;R1;r6])CC1)]"))
    mol.AddBond(*atoms, Chem.BondType.SINGLE)
    Chem.SanitizeMol(mol)
    self.assertEqual(Chem.MolToSmiles(mol), "C1CCC2C(C1)CC1C2C2C3CCCCC3C12")
    self.assertEqual(ri.NumRings(), 5)
    fusedRings = [ri.IsRingFused(i) for i in range(ri.NumRings())]
    self.assertEqual(fusedRings.count(True), 5)
    self.assertEqual(fusedRings.count(False), 0)
    fusedBonds = [ri.NumFusedBonds(i) for i in range(ri.NumRings())]
    self.assertEqual(fusedBonds.count(0), 0)
    self.assertEqual(fusedBonds.count(1), 2)
    self.assertEqual(fusedBonds.count(2), 3)

  def testNeedsHs(self):
    m = Chem.MolFromSmiles("CO")
    self.assertTrue(Chem.NeedsHs(m))
    mh = Chem.AddHs(m)
    self.assertFalse(Chem.NeedsHs(mh))
    nm = Chem.RWMol(mh)
    nm.RemoveAtom(3)
    self.assertTrue(Chem.NeedsHs(m))

  def testCountAtomElec(self):
    m = Chem.MolFromSmiles("c1n(C)ccc1")
    self.assertEqual(Chem.CountAtomElec(m.GetAtomWithIdx(0)), 1)
    self.assertEqual(Chem.CountAtomElec(m.GetAtomWithIdx(1)), 2)

  def testAtomHasConjugatedBond(self):
    m = Chem.MolFromSmiles("c1n(C)ccc1")
    self.assertTrue(Chem.AtomHasConjugatedBond(m.GetAtomWithIdx(1)))
    self.assertFalse(Chem.AtomHasConjugatedBond(m.GetAtomWithIdx(2)))

  def testSmilesWriteParamsForSMARTS(self):
    mol = Chem.MolFromSmiles('[NH3][Fe]N')
    self.assertIsNotNone(mol)
    ps = Chem.SmilesWriteParams()
    sma = Chem.MolToSmarts(mol, ps)
    self.assertEqual(sma, '[#7H3]->[Fe]-[#7]')
    ps.includeDativeBonds = False
    sma = Chem.MolToSmarts(mol, ps)
    self.assertEqual(sma, '[#7H3]-[Fe]-[#7]')

  def testMolToV2KMolBlock(self):
    mol = Chem.MolFromSmiles('[NH3]->[Fe]')
    self.assertIsNotNone(mol)
    mb = Chem.MolToV2KMolBlock(mol)
    self.assertTrue('V2000' in mb)
    self.assertTrue('  1  2  9  0' in mb)

  def testFindMesoCenters(self):
    mol = Chem.MolFromSmiles('C[C@@H](Cl)C[C@H](C)Cl')
    self.assertIsNotNone(mol)
    centers = Chem.FindMesoCenters(mol)
    self.assertEqual(len(centers), 1)
    expected = ((1, 4), )
    self.assertEqual(centers, expected)

    mol = Chem.MolFromSmiles('[CH3:1][C@@H](C)C[C@@H](C)[CH3:1]')
    self.assertIsNotNone(mol)
    centers = Chem.FindMesoCenters(mol, includeAtomMaps=True)
    self.assertEqual(len(centers), 1)
    expected = ((1, 4), )
    self.assertEqual(centers, expected)
    centers = Chem.FindMesoCenters(mol)
    self.assertEqual(centers, ())

    mol = Chem.MolFromSmiles('[13CH3][C@@H](C)C[C@@H](C)[13CH3]')
    self.assertIsNotNone(mol)
    centers = Chem.FindMesoCenters(mol)
    self.assertEqual(len(centers), 1)
    expected = ((1, 4), )
    self.assertEqual(centers, expected)
    centers = Chem.FindMesoCenters(mol, includeIsotopes=False)
    self.assertEqual(centers, ())

  def testIgnoreAtomMapNumbers(self):
    mol = Chem.MolFromSmiles("[NH2:1]c1ccccc1")
    ps = Chem.SmilesWriteParams()
    ps.ignoreAtomMapNumbers = True
    self.assertEqual(Chem.MolToSmiles(mol, ps), "[NH2:1]c1ccccc1")
    self.assertEqual(Chem.MolToSmiles(mol, ignoreAtomMapNumbers=True),
                     "[NH2:1]c1ccccc1")
    self.assertEqual(Chem.MolToSmiles(mol, ignoreAtomMapNumbers=False),
                     "c1ccc([NH2:1])cc1")

  def github7873(self):
    # this used to segfault
    Chem.MolFromSmiles("C").GetAtomWithIdx(0).SetPDBResidueInfo(None)
    
  def testSplitMolByPDB(self):
    mol = Chem.MolFromSmiles("C")
    res2mol = Chem.SplitMolByPDBResidues(mol)
    self.assertEqual(len(res2mol), 1)
    self.assertIn("", res2mol)
    chain2mol = Chem.SplitMolByPDBChainId(mol)
    self.assertEqual(len(chain2mol), 1)
    self.assertIn("", chain2mol)
    
  def testGithub4570(self):
    # test sending None to get/set prop
    m = Chem.Mol()
    # these should not seg fault
    for func in ["HasProp", "GetProp", "GetIntProp", "GetDoubleProp"]:
      try:
        getattr(m, func)(None)
      except Exception as e:
        assert "Python argument types" in str(e), f"{func}: {str(e)}"
    
    for func, val in [("SetProp", ""), ("SetIntProp", 1), ("SetDoubleProp", 1.0)]:
      try:
        getattr(m, func)(None, val)
      except Exception as e:
        assert "Python argument types" in str(e), f"{func}: {str(e)}"
      
    
if __name__ == '__main__':
  if "RDTESTCASE" in os.environ:
    suite = unittest.TestSuite()
    testcases = os.environ["RDTESTCASE"]
    for name in testcases.split(':'):
      suite.addTest(TestCase(name))

    runner = unittest.TextTestRunner()
    runner.run(suite)
  else:
    unittest.main()
