From 9214d4becb4bbeeeb6e19a49a00fc9aca0e2bf2c Mon Sep 17 00:00:00 2001 From: Greg Landrum Date: Tue, 24 Jan 2023 18:16:26 +0100 Subject: [PATCH] Support Python 3.11 (#5994) * remove some more deprecated numpy stuff * workaround for changes to random.shuffle in python 3.11 * fix pickling of rdkit mols in python 3.11 * add py311 build to CI * update py311 CI * remove qt for py311 for the moment * only use the new code with pyversion >=3.11 * use the new logic for all pickle_suites * need to work with older py too --- .azure-pipelines/linux_build_py311.yml | 83 +++++++++++++++++++ .../Wrap/FreeChemicalFeature.cpp | 2 +- Code/DataStructs/Wrap/DiscreteValueVect.cpp | 2 +- Code/DataStructs/Wrap/SparseIntVect.cpp | 2 +- Code/DataStructs/Wrap/wrap_ExplicitBV.cpp | 2 +- Code/DataStructs/Wrap/wrap_SparseBV.cpp | 2 +- Code/DistGeom/Wrap/rough_test.py | 16 ++-- Code/Geometry/Wrap/Point.cpp | 6 +- Code/Geometry/Wrap/UniformGrid3D.cpp | 2 +- .../ChemReactions/Wrap/rdChemReactions.cpp | 2 +- Code/GraphMol/Depictor/Wrap/testDepictor.py | 2 +- .../FilterCatalog/Wrap/FilterCatalog.cpp | 2 +- .../GraphMol/FragCatalog/Wrap/FragCatalog.cpp | 2 +- .../GraphMol/MolCatalog/Wrap/rdMolCatalog.cpp | 4 +- .../Wrap/rdScaffoldNetwork.cpp | 4 +- .../Wrap/SubstructLibraryWrap.cpp | 2 +- .../TautomerQuery/Wrap/rdTautomerQuery.cpp | 2 +- Code/GraphMol/Wrap/Mol.cpp | 2 +- Code/GraphMol/Wrap/rough_test.py | 10 +++ Code/Numerics/Alignment/Wrap/testAlignment.py | 20 ++--- Code/RDBoost/Wrap.h | 30 ++++++- azure-pipelines.yml | 12 +++ rdkit/Chem/BRICS.py | 3 +- rdkit/Chem/BuildFragmentCatalog.py | 4 +- rdkit/Chem/Randomize.py | 2 +- rdkit/ML/Data/DataUtils.py | 2 +- rdkit/ML/Data/SplitData.py | 2 +- rdkit/ML/DecTree/BuildQuantTree.py | 2 +- rdkit/ML/InfoTheory/UnitTestCorrMatGen.py | 2 +- rdkit/ML/ModelPackage/UnitTestPackage.py | 2 +- rdkit/ML/ScreenComposite.py | 2 +- rdkit/RDRandom.py | 30 ++++++- 32 files changed, 208 insertions(+), 54 deletions(-) create mode 100644 .azure-pipelines/linux_build_py311.yml diff --git a/.azure-pipelines/linux_build_py311.yml b/.azure-pipelines/linux_build_py311.yml new file mode 100644 index 000000000..569e03293 --- /dev/null +++ b/.azure-pipelines/linux_build_py311.yml @@ -0,0 +1,83 @@ +steps: +- bash: | + sudo apt-get install g++ wget make libgl1-mesa-dev mesa-common-dev + source ${CONDA}/etc/profile.d/conda.sh + sudo chown -R ${USER} ${CONDA} + conda config --set always_yes yes --set changeps1 no + conda update -q conda + conda info -a + conda create --name rdkit_build -c conda-forge $(python) cmake \ + boost-cpp=$(boost_version) \ + boost=$(boost_version) \ + numpy pillow eigen pandas matplotlib-base \ + cairo + conda activate rdkit_build + conda install -c conda-forge nbval ipykernel>=6.0 + displayName: Setup build environment (no Qt due to versioning problems) +- bash: | + source ${CONDA}/etc/profile.d/conda.sh + conda activate rdkit_build + mkdir build && cd build && \ + cmake .. \ + -DCMAKE_BUILD_TYPE=Release \ + -DRDK_INSTALL_INTREE=ON \ + -DRDK_INSTALL_STATIC_LIBS=OFF \ + -DRDK_BUILD_CPP_TESTS=ON \ + -DRDK_BUILD_PYTHON_WRAPPERS=ON \ + -DRDK_USE_BOOST_REGEX=ON \ + -DRDK_BUILD_COORDGEN_SUPPORT=ON \ + -DRDK_BUILD_MAEPARSER_SUPPORT=ON \ + -DRDK_OPTIMIZE_POPCNT=ON \ + -DRDK_BUILD_TEST_GZIP=ON \ + -DRDK_BUILD_FREESASA_SUPPORT=ON \ + -DRDK_BUILD_AVALON_SUPPORT=ON \ + -DRDK_BUILD_INCHI_SUPPORT=ON \ + -DRDK_BUILD_YAEHMOP_SUPPORT=ON \ + -DRDK_BUILD_XYZ2MOL_SUPPORT=ON \ + -DRDK_BUILD_CAIRO_SUPPORT=ON \ + -DRDK_BUILD_SWIG_WRAPPERS=OFF \ + -DRDK_SWIG_STATIC=OFF \ + -DRDK_BUILD_THREADSAFE_SSS=ON \ + -DRDK_TEST_MULTITHREADED=ON \ + -DRDK_BUILD_CFFI_LIB=ON \ + -DBoost_NO_SYSTEM_PATHS=ON \ + -DBoost_NO_BOOST_CMAKE=TRUE \ + -DRDK_BOOST_PYTHON3_NAME=$(python_name) \ + -DPYTHON_EXECUTABLE=${CONDA_PREFIX}/bin/python3 \ + -DCMAKE_INCLUDE_PATH="${CONDA_PREFIX}/include" \ + -DCMAKE_LIBRARY_PATH="${CONDA_PREFIX}/lib" + displayName: Configure build (Run CMake) +- bash: | + source ${CONDA}/etc/profile.d/conda.sh + conda activate rdkit_build + cd build + make -j $( $(number_of_cores) ) install + displayName: Build +- bash: | + source ${CONDA}/etc/profile.d/conda.sh + conda activate rdkit_build + export RDBASE=`pwd` + export PYTHONPATH=${RDBASE}:${PYTHONPATH} + export LD_LIBRARY_PATH=${RDBASE}/lib:${CONDA_PREFIX}/lib:${LD_LIBRARY_PATH} + echo "LD_LIBRARY_PATH: " $LD_LIBRARY_PATH + export QT_QPA_PLATFORM='offscreen' + cd build + ctest -j $( $(number_of_cores) ) --output-on-failure -T Test + displayName: Run tests +- bash: | + source ${CONDA}/etc/profile.d/conda.sh + conda activate rdkit_build + conda install ipython matplotlib-base + conda install -c conda-forge sphinx myst-parser + export RDBASE=`pwd` + export PYTHONPATH=${RDBASE}:${PYTHONPATH} + export LD_LIBRARY_PATH=${RDBASE}/lib:${LD_LIBRARY_PATH} + export QT_QPA_PLATFORM='offscreen' + cd Docs/Book + make doctest + displayName: Run documentation tests +- task: PublishTestResults@2 + inputs: + testResultsFormat: 'CTest' + testResultsFiles: 'build/Testing/*/Test.xml' + testRunTitle: $(system.phasedisplayname) CTest Test Run diff --git a/Code/ChemicalFeatures/Wrap/FreeChemicalFeature.cpp b/Code/ChemicalFeatures/Wrap/FreeChemicalFeature.cpp index 6a27792ea..ccc9706a9 100644 --- a/Code/ChemicalFeatures/Wrap/FreeChemicalFeature.cpp +++ b/Code/ChemicalFeatures/Wrap/FreeChemicalFeature.cpp @@ -20,7 +20,7 @@ namespace ChemicalFeatures { // support pickling: -struct chemfeat_pickle_suite : python::pickle_suite { +struct chemfeat_pickle_suite : rdkit_pickle_suite { static python::tuple getinitargs(const FreeChemicalFeature &self) { std::string res = self.toString(); python::object retval = python::object( diff --git a/Code/DataStructs/Wrap/DiscreteValueVect.cpp b/Code/DataStructs/Wrap/DiscreteValueVect.cpp index 4b11af54a..66d3e2714 100644 --- a/Code/DataStructs/Wrap/DiscreteValueVect.cpp +++ b/Code/DataStructs/Wrap/DiscreteValueVect.cpp @@ -17,7 +17,7 @@ using namespace RDKit; -struct dvv_pickle_suite : python::pickle_suite { +struct dvv_pickle_suite : rdkit_pickle_suite { static python::tuple getinitargs(const DiscreteValueVect& self) { std::string res = self.toString(); python::object retval = python::object( diff --git a/Code/DataStructs/Wrap/SparseIntVect.cpp b/Code/DataStructs/Wrap/SparseIntVect.cpp index ece074f67..7f762f477 100644 --- a/Code/DataStructs/Wrap/SparseIntVect.cpp +++ b/Code/DataStructs/Wrap/SparseIntVect.cpp @@ -31,7 +31,7 @@ python::object SIVToBinaryText(const SparseIntVect &siv) { } // namespace template -struct siv_pickle_suite : python::pickle_suite { +struct siv_pickle_suite : rdkit_pickle_suite { static python::tuple getinitargs(const SparseIntVect &self) { return python::make_tuple(SIVToBinaryText(self)); }; diff --git a/Code/DataStructs/Wrap/wrap_ExplicitBV.cpp b/Code/DataStructs/Wrap/wrap_ExplicitBV.cpp index 99ab4632b..9d6c63e5a 100644 --- a/Code/DataStructs/Wrap/wrap_ExplicitBV.cpp +++ b/Code/DataStructs/Wrap/wrap_ExplicitBV.cpp @@ -15,7 +15,7 @@ namespace python = boost::python; // allows BitVects to be pickled -struct ebv_pickle_suite : python::pickle_suite { +struct ebv_pickle_suite : rdkit_pickle_suite { static python::tuple getinitargs(const ExplicitBitVect &self) { std::string res = self.toString(); python::object retval = python::object( diff --git a/Code/DataStructs/Wrap/wrap_SparseBV.cpp b/Code/DataStructs/Wrap/wrap_SparseBV.cpp index 91870e5c2..d61b15200 100644 --- a/Code/DataStructs/Wrap/wrap_SparseBV.cpp +++ b/Code/DataStructs/Wrap/wrap_SparseBV.cpp @@ -17,7 +17,7 @@ namespace python = boost::python; // allows BitVects to be pickled -struct sbv_pickle_suite : python::pickle_suite { +struct sbv_pickle_suite : rdkit_pickle_suite { static python::tuple getinitargs(const SparseBitVect &self) { std::string res = self.toString(); python::object retval = python::object( diff --git a/Code/DistGeom/Wrap/rough_test.py b/Code/DistGeom/Wrap/rough_test.py index 7b461a496..f33aa3807 100644 --- a/Code/DistGeom/Wrap/rough_test.py +++ b/Code/DistGeom/Wrap/rough_test.py @@ -25,7 +25,7 @@ class TestCase(unittest.TestCase): pass def test1SmoothPass(self): - arr = np.array([[0, 1.0, 5.0], [1.0, 0, 1.0], [0.0, 1.0, 0]], np.float) + arr = np.array([[0, 1.0, 5.0], [1.0, 0, 1.0], [0.0, 1.0, 0]], float) self.assertTrue(DG.DoTriangleSmoothing(arr)) self.assertTrue(feq(arr[0, 2], 2.0)) self.assertTrue(feq(arr[2, 0], 0.0)) @@ -34,11 +34,11 @@ class TestCase(unittest.TestCase): self.assertTrue(feq(arr[1, 2], 1.0)) def test2SmoothFail(self): - arr = np.array([[0, 1.0, 5.0], [1.0, 0, 1.0], [3.0, 1.0, 0]], np.float) + arr = np.array([[0, 1.0, 5.0], [1.0, 0, 1.0], [3.0, 1.0, 0]], float) self.assertFalse(DG.DoTriangleSmoothing(arr)) def test3SmoothPass(self): - arr = np.array([[0, 1.1, 5.0], [0.9, 0, 1.1], [0.0, 0.9, 0]], np.float) + arr = np.array([[0, 1.1, 5.0], [0.9, 0, 1.1], [0.0, 0.9, 0]], float) self.assertTrue(DG.DoTriangleSmoothing(arr)) self.assertTrue(feq(arr[0, 2], 2.2)) self.assertTrue(feq(arr[2, 0], 0.0)) @@ -47,7 +47,7 @@ class TestCase(unittest.TestCase): self.assertTrue(feq(arr[1, 2], 1.1)) def test4Embed(self): - arr = np.array([[0, 1.0, 5.0], [1.0, 0, 1.0], [0.0, 1.0, 0]], np.float) + arr = np.array([[0, 1.0, 5.0], [1.0, 0, 1.0], [0.0, 1.0, 0]], float) self.assertTrue(DG.DoTriangleSmoothing(arr)) coords = DG.EmbedBoundsMatrix(arr, randomSeed=100) v1 = coords[0] - coords[1] @@ -58,13 +58,13 @@ class TestCase(unittest.TestCase): self.assertTrue(feq(d2, 1.0, 0.001)) def test5EmbedFail(self): - arr = np.array([[0, 1.0, 5.0], [1.0, 0, 1.0], [3.0, 1.0, 0]], np.float) + arr = np.array([[0, 1.0, 5.0], [1.0, 0, 1.0], [3.0, 1.0, 0]], float) self.assertRaises(ValueError, lambda: DG.EmbedBoundsMatrix(arr)) #DG.EmbedBoundsMatrix(arr,randomizeOnFailure=0,randomSeed=1) DG.EmbedBoundsMatrix(arr, randomizeOnFailure=1) def test6EmbedConstraints(self): - arr = np.array([[0.0, 1.0, 1.0], [1.0, 0.0, 1.0], [0.99, 1.0, 0.0]], np.float) + arr = np.array([[0.0, 1.0, 1.0], [1.0, 0.0, 1.0], [0.99, 1.0, 0.0]], float) self.assertTrue(DG.DoTriangleSmoothing(arr)) coords = DG.EmbedBoundsMatrix(arr, randomSeed=100) v1 = coords[0] - coords[1] @@ -75,7 +75,7 @@ class TestCase(unittest.TestCase): d2 = np.dot(v2, v2) self.assertTrue(feq(d2, 1.0, 2e-3)) arr = np.array([[0.0, 1.0, 1.0, 1.01], [1.0, 0.0, 1.0, 1.0], [1.0, 1.0, 0.0, 1.0], - [0.99, 1.0, 1.0, 0.0]], np.float) + [0.99, 1.0, 1.0, 0.0]], float) self.assertTrue(DG.DoTriangleSmoothing(arr)) coords = DG.EmbedBoundsMatrix(arr) v1 = coords[0] - coords[1] @@ -92,7 +92,7 @@ class TestCase(unittest.TestCase): arr = np.array([[0.0, 1.0, 1.0, 1.0], [1.0, 0.0, 1.0, 1.0], [1.0, 1.0, 0.0, 1.0], - [1.0, 1.0, 1.0, 0.0], ], np.float) + [1.0, 1.0, 1.0, 0.0], ], float) self.assertTrue(DG.DoTriangleSmoothing(arr)) coords = DG.EmbedBoundsMatrix(arr, randomSeed=100) v1 = coords[0] - coords[1] diff --git a/Code/Geometry/Wrap/Point.cpp b/Code/Geometry/Wrap/Point.cpp index 167d10d66..7ae90b1cc 100644 --- a/Code/Geometry/Wrap/Point.cpp +++ b/Code/Geometry/Wrap/Point.cpp @@ -15,17 +15,17 @@ namespace python = boost::python; namespace { -struct Point3D_pickle_suite : python::pickle_suite { +struct Point3D_pickle_suite : rdkit_pickle_suite { static python::tuple getinitargs(RDGeom::Point3D const &pt) { return python::make_tuple(pt.x, pt.y, pt.z); } }; -struct Point2D_pickle_suite : python::pickle_suite { +struct Point2D_pickle_suite : rdkit_pickle_suite { static python::tuple getinitargs(RDGeom::Point2D const &pt) { return python::make_tuple(pt.x, pt.y); } }; -struct PointND_pickle_suite : python::pickle_suite { +struct PointND_pickle_suite : rdkit_pickle_suite { static python::tuple getinitargs(RDGeom::PointND const &pt) { return python::make_tuple(pt.dimension()); } diff --git a/Code/Geometry/Wrap/UniformGrid3D.cpp b/Code/Geometry/Wrap/UniformGrid3D.cpp index a164c6f68..ee96bb468 100644 --- a/Code/Geometry/Wrap/UniformGrid3D.cpp +++ b/Code/Geometry/Wrap/UniformGrid3D.cpp @@ -23,7 +23,7 @@ namespace python = boost::python; using namespace RDKit; namespace RDGeom { -struct ug3d_pickle_suite : python::pickle_suite { +struct ug3d_pickle_suite : rdkit_pickle_suite { static python::tuple getinitargs(const UniformGrid3D &self) { std::string res = self.toString(); python::object retval = python::object( diff --git a/Code/GraphMol/ChemReactions/Wrap/rdChemReactions.cpp b/Code/GraphMol/ChemReactions/Wrap/rdChemReactions.cpp index ca18c88cf..6fc960ae0 100644 --- a/Code/GraphMol/ChemReactions/Wrap/rdChemReactions.cpp +++ b/Code/GraphMol/ChemReactions/Wrap/rdChemReactions.cpp @@ -82,7 +82,7 @@ python::object ReactionToBinary(const ChemicalReaction &self) { // // allows reactions to be pickled. // -struct reaction_pickle_suite : python::pickle_suite { +struct reaction_pickle_suite : rdkit_pickle_suite { static python::tuple getinitargs(const ChemicalReaction &self) { return python::make_tuple(ReactionToBinary(self)); }; diff --git a/Code/GraphMol/Depictor/Wrap/testDepictor.py b/Code/GraphMol/Depictor/Wrap/testDepictor.py index 48f43344e..d819c472c 100755 --- a/Code/GraphMol/Depictor/Wrap/testDepictor.py +++ b/Code/GraphMol/Depictor/Wrap/testDepictor.py @@ -28,7 +28,7 @@ def getDistMat(mol): conf = mol.GetConformer() nat = mol.GetNumAtoms() nl = nat * (nat - 1) // 2 - res = np.zeros(nl, np.float) + res = np.zeros(nl, float) for i in range(1, nat): pi = conf.GetAtomPosition(i) diff --git a/Code/GraphMol/FilterCatalog/Wrap/FilterCatalog.cpp b/Code/GraphMol/FilterCatalog/Wrap/FilterCatalog.cpp index e37fd3dbf..8bed9c2cf 100644 --- a/Code/GraphMol/FilterCatalog/Wrap/FilterCatalog.cpp +++ b/Code/GraphMol/FilterCatalog/Wrap/FilterCatalog.cpp @@ -42,7 +42,7 @@ namespace python = boost::python; namespace RDKit { -struct filtercatalog_pickle_suite : python::pickle_suite { +struct filtercatalog_pickle_suite : rdkit_pickle_suite { static python::tuple getinitargs(const FilterCatalog &self) { std::string res; if (!FilterCatalogCanSerialize()) { diff --git a/Code/GraphMol/FragCatalog/Wrap/FragCatalog.cpp b/Code/GraphMol/FragCatalog/Wrap/FragCatalog.cpp index 9fc2293cd..0fb65b252 100644 --- a/Code/GraphMol/FragCatalog/Wrap/FragCatalog.cpp +++ b/Code/GraphMol/FragCatalog/Wrap/FragCatalog.cpp @@ -19,7 +19,7 @@ namespace python = boost::python; namespace RDKit { -struct fragcatalog_pickle_suite : python::pickle_suite { +struct fragcatalog_pickle_suite : rdkit_pickle_suite { static python::tuple getinitargs(const FragCatalog &self) { std::string res; res = self.Serialize(); diff --git a/Code/GraphMol/MolCatalog/Wrap/rdMolCatalog.cpp b/Code/GraphMol/MolCatalog/Wrap/rdMolCatalog.cpp index 6864144ac..06e5e2a9f 100644 --- a/Code/GraphMol/MolCatalog/Wrap/rdMolCatalog.cpp +++ b/Code/GraphMol/MolCatalog/Wrap/rdMolCatalog.cpp @@ -12,7 +12,7 @@ namespace python = boost::python; using namespace RDKit; namespace { -struct molcatalog_pickle_suite : python::pickle_suite { +struct molcatalog_pickle_suite : rdkit_pickle_suite { static python::tuple getinitargs(const MolCatalog &self) { std::string res; res = self.Serialize(); @@ -21,7 +21,7 @@ struct molcatalog_pickle_suite : python::pickle_suite { }; }; -struct molcatalogentry_pickle_suite : python::pickle_suite { +struct molcatalogentry_pickle_suite : rdkit_pickle_suite { static python::tuple getinitargs(const MolCatalogEntry &self) { std::string res; res = self.Serialize(); diff --git a/Code/GraphMol/ScaffoldNetwork/Wrap/rdScaffoldNetwork.cpp b/Code/GraphMol/ScaffoldNetwork/Wrap/rdScaffoldNetwork.cpp index 262296d00..9363f754b 100644 --- a/Code/GraphMol/ScaffoldNetwork/Wrap/rdScaffoldNetwork.cpp +++ b/Code/GraphMol/ScaffoldNetwork/Wrap/rdScaffoldNetwork.cpp @@ -48,7 +48,7 @@ ScaffoldNetwork::ScaffoldNetworkParams *getBRICSParams() { } // namespace #ifdef RDK_USE_BOOST_SERIALIZATION -struct scaffoldnetwork_pickle_suite : python::pickle_suite { +struct scaffoldnetwork_pickle_suite : rdkit_pickle_suite { static python::tuple getinitargs( const RDKit::ScaffoldNetwork::ScaffoldNetwork &self) { std::stringstream oss; @@ -60,7 +60,7 @@ struct scaffoldnetwork_pickle_suite : python::pickle_suite { }; }; #else -struct scaffoldnetwork_pickle_suite : python::pickle_suite { +struct scaffoldnetwork_pickle_suite : rdkit_pickle_suite { static python::tuple getinitargs( const RDKit::ScaffoldNetwork::ScaffoldNetwork &self) { throw_runtime_error("Pickling of ScaffoldNetwork instances is not enabled"); diff --git a/Code/GraphMol/SubstructLibrary/Wrap/SubstructLibraryWrap.cpp b/Code/GraphMol/SubstructLibrary/Wrap/SubstructLibraryWrap.cpp index 1d72e2111..07b70bcbe 100644 --- a/Code/GraphMol/SubstructLibrary/Wrap/SubstructLibraryWrap.cpp +++ b/Code/GraphMol/SubstructLibrary/Wrap/SubstructLibraryWrap.cpp @@ -412,7 +412,7 @@ python::object SubstructLibrary_Serialize(const SubstructLibraryWrap &cat) { return retval; } -struct substructlibrary_pickle_suite : python::pickle_suite { +struct substructlibrary_pickle_suite : rdkit_pickle_suite { static python::tuple getinitargs(const SubstructLibraryWrap &self) { std::string res; if (!SubstructLibraryCanSerialize()) { diff --git a/Code/GraphMol/TautomerQuery/Wrap/rdTautomerQuery.cpp b/Code/GraphMol/TautomerQuery/Wrap/rdTautomerQuery.cpp index caf082644..4daa48cba 100644 --- a/Code/GraphMol/TautomerQuery/Wrap/rdTautomerQuery.cpp +++ b/Code/GraphMol/TautomerQuery/Wrap/rdTautomerQuery.cpp @@ -136,7 +136,7 @@ PyObject *tautomerGetSubstructMatchesWithTautomers( } // namespace namespace RDKit { -struct tautomerquery_pickle_suite : python::pickle_suite { +struct tautomerquery_pickle_suite : rdkit_pickle_suite { static python::tuple getinitargs(const TautomerQuery &self) { if (!TautomerQueryCanSerialize()) { throw_runtime_error("Pickling of TautomerQuery instances is not enabled"); diff --git a/Code/GraphMol/Wrap/Mol.cpp b/Code/GraphMol/Wrap/Mol.cpp index 2d8412c19..619004735 100644 --- a/Code/GraphMol/Wrap/Mol.cpp +++ b/Code/GraphMol/Wrap/Mol.cpp @@ -63,7 +63,7 @@ python::object MolToBinaryWithProps(const ROMol &self, unsigned int props) { // since molecules have a constructor that takes a binary string // we only need to provide getinitargs() // -struct mol_pickle_suite : python::pickle_suite { +struct mol_pickle_suite : rdkit_pickle_suite { static python::tuple getinitargs(const ROMol &self) { return python::make_tuple(MolToBinary(self)); }; diff --git a/Code/GraphMol/Wrap/rough_test.py b/Code/GraphMol/Wrap/rough_test.py index 70af8b4eb..d5119fbc0 100644 --- a/Code/GraphMol/Wrap/rough_test.py +++ b/Code/GraphMol/Wrap/rough_test.py @@ -16,6 +16,7 @@ import os import sys import tempfile import unittest +import pickle from contextlib import contextmanager from datetime import datetime, timedelta from io import StringIO @@ -6928,6 +6929,15 @@ CAS<~> Chem.SetUseLegacyStereoPerception(origVal) + 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) + if __name__ == '__main__': if "RDTESTCASE" in os.environ: diff --git a/Code/Numerics/Alignment/Wrap/testAlignment.py b/Code/Numerics/Alignment/Wrap/testAlignment.py index 98b852256..7cd08d429 100644 --- a/Code/Numerics/Alignment/Wrap/testAlignment.py +++ b/Code/Numerics/Alignment/Wrap/testAlignment.py @@ -38,8 +38,8 @@ class TestCase(unittest.TestCase): def test1Basic(self): # passing two numeric arrays - refPts = np.zeros((2, 3), np.float) - prbPts = np.zeros((2, 3), np.float) + refPts = np.zeros((2, 3), float) + prbPts = np.zeros((2, 3), float) refPts[1, 0] = 1.0 @@ -68,7 +68,7 @@ class TestCase(unittest.TestCase): self.assertTrue(feq(res[0], 0.0)) # mix it up - refPts = np.zeros((2, 3), np.float) + refPts = np.zeros((2, 3), float) refPts[1, 0] = 1.0 res = rdAlg.GetAlignmentTransform(refPts, prbPts) self.assertTrue(feq(res[0], 0.0)) @@ -76,10 +76,10 @@ class TestCase(unittest.TestCase): def test2Weights(self): refPts = np.array([[-math.cos(math.pi / 6), -math.sin(math.pi / 6), 0.0], [math.cos(math.pi / 6), -math.sin(math.pi / 6), 0.0], [0.0, 1.0, 0.0]], - np.float) + float) prbPts = np.array([[-2 * math.sin(math.pi / 6) + 3.0, 2 * math.cos(math.pi / 6), 4.0], [-2 * math.sin(math.pi / 6) + 3.0, -2 * math.cos(math.pi / 6), 4.0], - [5.0, 0.0, 4.0]], np.float) + [5.0, 0.0, 4.0]], float) res = rdAlg.GetAlignmentTransform(refPts, prbPts) self.assertTrue(feq(res[0], 3.0)) target = [[-1.732, -1., 0.], [1.732, -1., 0.], [0., 2., 0.]] @@ -88,7 +88,7 @@ class TestCase(unittest.TestCase): self.assertTrue(lstFeq(transformPoint(res[1], item), target[cnt])) cnt += 1 - weights = np.array([1.0, 1.0, 2.0], np.float) + weights = np.array([1.0, 1.0, 2.0], float) res = rdAlg.GetAlignmentTransform(refPts, prbPts, weights) self.assertTrue(feq(res[0], 3.75)) cnt = 0 @@ -106,12 +106,12 @@ class TestCase(unittest.TestCase): def test3tetra(self): refPts = np.array([[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]], - np.float) - prbPts = np.array([[2.0, 2.0, 3.0], [3.0, 2.0, 3.0], [2.0, 3.0, 3.0]], np.float) + float) + prbPts = np.array([[2.0, 2.0, 3.0], [3.0, 2.0, 3.0], [2.0, 3.0, 3.0]], float) self.assertRaises(ValueError, lambda: rdAlg.GetAlignmentTransform(refPts, prbPts)) prbPts = np.array([[2.0, 2.0, 3.0], [3.0, 2.0, 3.0], [2.0, 3.0, 3.0], [2.0, 2.0, 4.0]], - np.float) + float) res = rdAlg.GetAlignmentTransform(refPts, prbPts) self.assertTrue(feq(res[0], 0.0)) @@ -124,7 +124,7 @@ class TestCase(unittest.TestCase): # test reflection prbPts = np.array([[2.0, 2.0, 3.0], [3.0, 2.0, 3.0], [2.0, 2.0, 4.0], [2.0, 3.0, 3.0]], - np.float) + float) res = rdAlg.GetAlignmentTransform(refPts, prbPts, wts) self.assertTrue(feq(res[0], 1.0)) diff --git a/Code/RDBoost/Wrap.h b/Code/RDBoost/Wrap.h index 4ce7c0cc0..c9c7eff6f 100644 --- a/Code/RDBoost/Wrap.h +++ b/Code/RDBoost/Wrap.h @@ -258,10 +258,10 @@ struct iterable_converter { /// provided type. template iterable_converter &from_python() { - boost::python::converter::registry::push_back( + python::converter::registry::push_back( &iterable_converter::convertible, &iterable_converter::construct, - boost::python::type_id()); + python::type_id()); // Support chaining. return *this; @@ -282,7 +282,7 @@ struct iterable_converter { template static void construct( PyObject *object, - boost::python::converter::rvalue_from_python_stage1_data *data) { + python::converter::rvalue_from_python_stage1_data *data) { namespace python = boost::python; // Object is a borrowed reference, so create a handle indicting it is // borrowed for proper reference counting. @@ -306,4 +306,28 @@ struct iterable_converter { } }; +// +// allows rdkit objects to be pickled. +struct rdkit_pickle_suite : python::pickle_suite { + static python::tuple getstate(python::object w_obj) { + return python::make_tuple(w_obj.attr("__dict__")); + } + + static void setstate(python::object w_obj, python::tuple state) { + if (len(state) != 1) { + PyErr_SetObject( + PyExc_ValueError, + ("expected 1-item tuple in call to __setstate__; got %s" % state) + .ptr()); + python::throw_error_already_set(); + } + + // restore the object's __dict__ + python::dict d = python::extract(w_obj.attr("__dict__"))(); + d.update(state[0]); + } + + static bool getstate_manages_dict() { return true; } +}; + #endif diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 39e7f701f..467d0e299 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -16,6 +16,18 @@ jobs: python_name: python38 steps: - template: .azure-pipelines/linux_build.yml +- job: Ubuntu_x64_py311 + timeoutInMinutes: 90 + pool: + vmImage: ubuntu-20.04 + variables: + python: python=3.11 + boost_version: 1.80.0 + compiler: gxx_linux-64 + number_of_cores: nproc + python_name: python311 + steps: + - template: .azure-pipelines/linux_build_py311.yml - job: macOS_x64 timeoutInMinutes: 90 pool: diff --git a/rdkit/Chem/BRICS.py b/rdkit/Chem/BRICS.py index a292aa4dc..045fe683b 100644 --- a/rdkit/Chem/BRICS.py +++ b/rdkit/Chem/BRICS.py @@ -29,14 +29,13 @@ # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # # Created by Greg Landrum, Nov 2008 -import random import copy """ Implementation of the BRICS algorithm from Degen et al. ChemMedChem *3* 1503-7 (2008) """ import sys import re -import random +from rdkit import RDRandom as random from rdkit import Chem from rdkit.Chem import rdChemReactions as Reactions diff --git a/rdkit/Chem/BuildFragmentCatalog.py b/rdkit/Chem/BuildFragmentCatalog.py index b1925d932..98b23f85e 100755 --- a/rdkit/Chem/BuildFragmentCatalog.py +++ b/rdkit/Chem/BuildFragmentCatalog.py @@ -161,7 +161,7 @@ def ScoreMolecules(suppl, catalog, maxPts=-1, actName='', acts=None, nActs=2, re """ nBits = catalog.GetFPLength() - resTbl = numpy.zeros((nBits, 2, nActs), numpy.int) + resTbl = numpy.zeros((nBits, 2, nActs), numpy.int32) obls = [] if not actName and not acts: @@ -225,7 +225,7 @@ def ScoreFromLists(bitLists, suppl, catalog, maxPts=-1, actName='', acts=None, n nPts = maxPts else: nPts = len(bitLists) - resTbl = numpy.zeros((nBits, 2, nActs), numpy.int) + resTbl = numpy.zeros((nBits, 2, nActs), numpy.int32) if not actName and not acts: actName = suppl[0].GetPropNames()[-1] suppl.reset() diff --git a/rdkit/Chem/Randomize.py b/rdkit/Chem/Randomize.py index ef7459821..416bc1360 100644 --- a/rdkit/Chem/Randomize.py +++ b/rdkit/Chem/Randomize.py @@ -8,7 +8,7 @@ # which is included in the file license.txt, found at the root # of the RDKit source tree. # -import random +from rdkit import RDRandom as random from rdkit import Chem diff --git a/rdkit/ML/Data/DataUtils.py b/rdkit/ML/Data/DataUtils.py index e084cdf04..a3dce580f 100755 --- a/rdkit/ML/Data/DataUtils.py +++ b/rdkit/ML/Data/DataUtils.py @@ -52,7 +52,7 @@ import csv -import random +from rdkit import RDRandom as random import re import numpy diff --git a/rdkit/ML/Data/SplitData.py b/rdkit/ML/Data/SplitData.py index ac12a9abc..75dc48ac9 100755 --- a/rdkit/ML/Data/SplitData.py +++ b/rdkit/ML/Data/SplitData.py @@ -121,7 +121,7 @@ def SplitIndices(nPts, frac, silent=1, legacy=0, replacement=0): resTest.append(i) else: perm = list(range(nPts)) - random.shuffle(perm, random=random.random) + RDRandom.shuffle(perm, random=random.random) nTrain = int(nPts * frac) resData = list(perm[:nTrain]) diff --git a/rdkit/ML/DecTree/BuildQuantTree.py b/rdkit/ML/DecTree/BuildQuantTree.py index 6d583e794..6e993d17d 100755 --- a/rdkit/ML/DecTree/BuildQuantTree.py +++ b/rdkit/ML/DecTree/BuildQuantTree.py @@ -8,7 +8,7 @@ """ import numpy -import random +from rdkit import RDRandom as random from rdkit.ML.DecTree import QuantTree, ID3 from rdkit.ML.InfoTheory import entropy from rdkit.ML.Data import Quantize diff --git a/rdkit/ML/InfoTheory/UnitTestCorrMatGen.py b/rdkit/ML/InfoTheory/UnitTestCorrMatGen.py index d35fc0c4c..a4a8e59cd 100755 --- a/rdkit/ML/InfoTheory/UnitTestCorrMatGen.py +++ b/rdkit/ML/InfoTheory/UnitTestCorrMatGen.py @@ -2,7 +2,7 @@ from rdkit.ML.InfoTheory import rdInfoTheory from rdkit.ML.Data import DataUtils from rdkit import DataStructs import unittest -import random +from rdkit import RDRandom as random try: diff --git a/rdkit/ML/ModelPackage/UnitTestPackage.py b/rdkit/ML/ModelPackage/UnitTestPackage.py index 1abc0cd17..167210a19 100644 --- a/rdkit/ML/ModelPackage/UnitTestPackage.py +++ b/rdkit/ML/ModelPackage/UnitTestPackage.py @@ -3,7 +3,7 @@ # """ unit tests for the model and descriptor packager """ import os -import random +from rdkit import RDRandom as random import unittest from xml.dom import minidom from xml.etree import ElementTree as ET diff --git a/rdkit/ML/ScreenComposite.py b/rdkit/ML/ScreenComposite.py index 5bc41ae00..f2056c1d1 100755 --- a/rdkit/ML/ScreenComposite.py +++ b/rdkit/ML/ScreenComposite.py @@ -426,7 +426,7 @@ def ShowVoteResults(indices, data, composite, nResultCodes, threshold, verbose=1 print('average correct confidence: % 6.4f' % avgGood) print('average incorrect confidence: % 6.4f' % avgBad) - voteTab = numpy.zeros((nResultCodes, nResultCodes), numpy.int) + voteTab = numpy.zeros((nResultCodes, nResultCodes), numpy.int32) for res in goodRes: voteTab[res, res] += 1 for ans, res, conf, idx in badVotes: diff --git a/rdkit/RDRandom.py b/rdkit/RDRandom.py index 3bbbe703a..bfefd5079 100644 --- a/rdkit/RDRandom.py +++ b/rdkit/RDRandom.py @@ -1,6 +1,5 @@ -# $Id$ # -# Copyright (C) 2003-2006 Greg Landrum and Rational Discovery LLC +# Copyright (C) 2003-2023 Greg Landrum and other RDKit contributors # # @@ All Rights Reserved @@ # This file is part of the RDKit. @@ -18,3 +17,30 @@ import random as _random random = _random.random randrange = _random.randrange seed = _random.seed + +if sys.hexversion >= 0x30b0000: + # Python 3.11 deprecated the random argument to random.shuffle, which changed + # the behavior and made it impossible to exactly reproduce old results. this is + # a slightly adapted form of random.shuffle from python 3.10: + # https://github.com/python/cpython/blob/b05352e4c2f25b292fb7de0ab927e74415bc2dd8/Lib/random.py#LL380-L404C40 + def shuffle(x, random=None): + """Shuffle list x in place, and return None. + Optional argument random is a 0-argument function returning a + random float in [0.0, 1.0); if it is the default None, the + standard random.random will be used. + """ + + if random is None: + randbelow = _random._randbelow + for i in reversed(range(1, len(x))): + # pick an element in x[:i+1] with which to exchange x[i] + j = randbelow(i + 1) + x[i], x[j] = x[j], x[i] + else: + floor = _random._floor + for i in reversed(range(1, len(x))): + # pick an element in x[:i+1] with which to exchange x[i] + j = floor(random() * (i + 1)) + x[i], x[j] = x[j], x[i] +else: + shuffle = _random.shuffle \ No newline at end of file