From 0f951643bd1395a11068a6132f8e773d40c2d268 Mon Sep 17 00:00:00 2001 From: Grady Williams Date: Tue, 15 Feb 2022 13:52:12 -0800 Subject: [PATCH 001/113] Adding failing tests for ISAM2 marginalization --- tests/testGaussianISAM2.cpp | 150 ++++++++++++++++++++++++++++++++++++ 1 file changed, 150 insertions(+) diff --git a/tests/testGaussianISAM2.cpp b/tests/testGaussianISAM2.cpp index e8e916f04..efe34ee31 100644 --- a/tests/testGaussianISAM2.cpp +++ b/tests/testGaussianISAM2.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -662,6 +663,76 @@ namespace { bool ok = treeEqual && /*linEqual &&*/ nonlinEqual && /*linCorrect &&*/ /*afterLinCorrect &&*/ afterNonlinCorrect; return ok; } + + boost::optional> createOrderingConstraints(const ISAM2& isam, const KeyVector& newKeys, const KeySet& marginalizableKeys) + { + if (marginalizableKeys.empty()) { + return boost::none; + } else { + FastMap constrainedKeys = FastMap(); + // Generate ordering constraints so that the marginalizable variables will be eliminated first + // Set all existing and new variables to Group1 + for (const auto& key_val : isam.getDelta()) { + constrainedKeys.emplace(key_val.first, 1); + } + for (const auto& key : newKeys) { + constrainedKeys.emplace(key, 1); + } + // And then re-assign the marginalizable variables to Group0 so that they'll all be leaf nodes + for (const auto& key : marginalizableKeys) { + constrainedKeys.at(key) = 0; + } + return constrainedKeys; + } + } + + void markAffectedKeys(const Key& key, const ISAM2Clique::shared_ptr& rootClique, KeyList& additionalKeys) + { + std::stack frontier; + frontier.push(rootClique); + // Basic DFS to find additional keys + while (!frontier.empty()) { + // Get the top of the stack + const ISAM2Clique::shared_ptr clique = frontier.top(); + frontier.pop(); + // Check if we have more keys and children to add + if (std::find(clique->conditional()->beginParents(), clique->conditional()->endParents(), key) != + clique->conditional()->endParents()) { + for (Key i : clique->conditional()->frontals()) { + additionalKeys.push_back(i); + } + for (const ISAM2Clique::shared_ptr& child : clique->children) { + frontier.push(child); + } + } + } + } + + bool updateAndMarginalize(const NonlinearFactorGraph& newFactors, const Values& newValues, const KeySet& marginalizableKeys, ISAM2& isam) + { + // Force ISAM2 to put marginalizable variables at the beginning + const boost::optional> orderingConstraints = createOrderingConstraints(isam, newValues.keys(), marginalizableKeys); + + // Mark additional keys between the marginalized keys and the leaves + KeyList additionalMarkedKeys; + for (Key key : marginalizableKeys) { + ISAM2Clique::shared_ptr clique = isam[key]; + for (const ISAM2Clique::shared_ptr& child : clique->children) { + markAffectedKeys(key, child, additionalMarkedKeys); + } + } + + // Update + isam.update(newFactors, newValues, FactorIndices{}, orderingConstraints, boost::none, additionalMarkedKeys); + + if (!marginalizableKeys.empty()) { + FastList leafKeys(marginalizableKeys.begin(), marginalizableKeys.end()); + return checkMarginalizeLeaves(isam, leafKeys); + } + else { + return true; + } + } } /* ************************************************************************* */ @@ -795,6 +866,85 @@ TEST(ISAM2, marginalizeLeaves5) EXPECT(checkMarginalizeLeaves(isam, marginalizeKeys)); } +/* ************************************************************************* */ +TEST(ISAM2, marginalizeLeaves6) +{ + const boost::shared_ptr nm = noiseModel::Isotropic::Sigma(6, 1.0); + + int gridDim = 10; + + auto idxToKey = [gridDim](int i, int j){return i * gridDim + j;}; + + NonlinearFactorGraph factors; + Values values; + ISAM2 isam; + + // Create a grid of pose variables + for (int i = 0; i < gridDim; ++i) { + for (int j = 0; j < gridDim; ++j) { + Pose3 pose = Pose3(Rot3::identity(), Point3(i, j, 0)); + Key key = idxToKey(i, j); + // Place a prior on the first pose + factors.addPrior(key, Pose3(Rot3::identity(), Point3(i, j, 0)), nm); + values.insert(key, pose); + if (i > 0) { + factors.emplace_shared>(idxToKey(i - 1, j), key, Pose3(Rot3::identity(), Point3(1, 0, 0)),nm); + } + if (j > 0) { + factors.emplace_shared>(idxToKey(i, j - 1), key, Pose3(Rot3::identity(), Point3(0, 1, 0)),nm); + } + } + } + + // Optimize the graph + EXPECT(updateAndMarginalize(factors, values, {}, isam)); + auto estimate = isam.calculateBestEstimate(); + + // Get the list of keys + std::vector key_list(gridDim * gridDim); + std::iota(key_list.begin(), key_list.end(), 0); + + // Shuffle the keys -> we will marginalize the keys one by one in this order + std::shuffle(key_list.begin(), key_list.end(), std::default_random_engine(1234)); + + for (const auto& key : key_list) { + KeySet marginalKeys; + marginalKeys.insert(key); + EXPECT(updateAndMarginalize({}, {}, marginalKeys, isam)); + estimate = isam.calculateBestEstimate(); + } +} + +/* ************************************************************************* */ +TEST(ISAM2, MarginalizeRoot) +{ + const boost::shared_ptr nm = noiseModel::Isotropic::Sigma(6, 1.0); + + NonlinearFactorGraph factors; + Values values; + ISAM2 isam; + + // Create a factor graph with one variable and a prior + Pose3 root = Pose3::identity(); + Key rootKey(0); + values.insert(rootKey, root); + factors.addPrior(rootKey, Pose3::identity(), nm); + + // Optimize the graph + EXPECT(updateAndMarginalize(factors, values, {}, isam)); + auto estimate = isam.calculateBestEstimate(); + EXPECT(estimate.size() == 1); + + // And remove the node from the graph + KeySet marginalizableKeys; + marginalizableKeys.insert(rootKey); + + EXPECT(updateAndMarginalize({}, {}, marginalizableKeys, isam)); + + estimate = isam.calculateBestEstimate(); + EXPECT(estimate.empty()); +} + /* ************************************************************************* */ TEST(ISAM2, marginalCovariance) { From 9e1046f40e21bd66b4db5fe300aa0e46b39365a4 Mon Sep 17 00:00:00 2001 From: Grady Williams Date: Sat, 16 Apr 2022 13:53:39 -0700 Subject: [PATCH 002/113] Test for not increasing graph size when adding marginal factors --- tests/testGaussianISAM2.cpp | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/tests/testGaussianISAM2.cpp b/tests/testGaussianISAM2.cpp index efe34ee31..940051b96 100644 --- a/tests/testGaussianISAM2.cpp +++ b/tests/testGaussianISAM2.cpp @@ -945,6 +945,37 @@ TEST(ISAM2, MarginalizeRoot) EXPECT(estimate.empty()); } +/* ************************************************************************* */ +TEST(ISAM2, marginalizationSize) +{ + const boost::shared_ptr nm = noiseModel::Isotropic::Sigma(6, 1.0); + + NonlinearFactorGraph factors; + Values values; + ISAM2Params params; + params.findUnusedFactorSlots = true; + ISAM2 isam{params}; + + // Create a pose variable + Key aKey(0); + values.insert(aKey, Pose3::identity()); + factors.addPrior(aKey, Pose3::identity(), nm); + // Create another pose variable linked to the first + Pose3 b = Pose3::identity(); + Key bKey(1); + values.insert(bKey, Pose3::identity()); + factors.emplace_shared>(aKey, bKey, Pose3::identity(), nm); + // Optimize the graph + EXPECT(updateAndMarginalize(factors, values, {}, isam)); + + // Now remove a variable -> we should not see the number of factors increase + gtsam::KeySet to_remove; + to_remove.insert(aKey); + const auto numFactorsBefore = isam.getFactorsUnsafe().size(); + updateAndMarginalize({}, {}, to_remove, isam); + EXPECT(numFactorsBefore == isam.getFactorsUnsafe().size()); +} + /* ************************************************************************* */ TEST(ISAM2, marginalCovariance) { From 66720e0b0276360d1581335332714d2c7e1ecc93 Mon Sep 17 00:00:00 2001 From: Grady Williams Date: Sat, 16 Apr 2022 13:29:53 -0700 Subject: [PATCH 003/113] Bugfixes for ISAM2 --- gtsam/nonlinear/ISAM2.cpp | 66 +++++++++++++++++++++++-------------- tests/testGaussianISAM2.cpp | 7 ++-- 2 files changed, 45 insertions(+), 28 deletions(-) diff --git a/gtsam/nonlinear/ISAM2.cpp b/gtsam/nonlinear/ISAM2.cpp index f56b23777..a7feca239 100644 --- a/gtsam/nonlinear/ISAM2.cpp +++ b/gtsam/nonlinear/ISAM2.cpp @@ -552,9 +552,12 @@ void ISAM2::marginalizeLeaves( // We do not need the marginal factors associated with this clique // because their information is already incorporated in the new // marginal factor. So, now associate this marginal factor with the - // parent of this clique. - marginalFactors[clique->parent()->conditional()->front()].push_back( - marginalFactor); + // parent of this clique. If the clique is a root and has no parent, then + // we can discard it without keeping track of the marginal factor. + if (clique->parent()) { + marginalFactors[clique->parent()->conditional()->front()].push_back( + marginalFactor); + } // Now remove this clique and its subtree - all of its marginal // information has been stored in marginalFactors. trackingRemoveSubtree(clique); @@ -632,7 +635,7 @@ void ISAM2::marginalizeLeaves( // Make the clique's matrix appear as a subset const DenseIndex dimToRemove = cg->matrixObject().offset(nToRemove); - cg->matrixObject().firstBlock() = nToRemove; + cg->matrixObject().firstBlock() += nToRemove; cg->matrixObject().rowStart() = dimToRemove; // Change the keys in the clique @@ -658,42 +661,55 @@ void ISAM2::marginalizeLeaves( // At this point we have updated the BayesTree, now update the remaining iSAM2 // data structures + // Remove the factors to remove that will be summarized in marginal factors + NonlinearFactorGraph removedFactors; + for (const auto index : factorIndicesToRemove) { + removedFactors.push_back(nonlinearFactors_[index]); + nonlinearFactors_.remove(index); + if (params_.cacheLinearizedFactors) { + linearFactors_.remove(index); + } + } + variableIndex_.remove(factorIndicesToRemove.begin(), + factorIndicesToRemove.end(), removedFactors); + // Gather factors to add - the new marginal factors - GaussianFactorGraph factorsToAdd; + GaussianFactorGraph factorsToAdd{}; + NonlinearFactorGraph nonlinearFactorsToAdd{}; for (const auto& key_factors : marginalFactors) { for (const auto& factor : key_factors.second) { if (factor) { factorsToAdd.push_back(factor); - if (marginalFactorsIndices) - marginalFactorsIndices->push_back(nonlinearFactors_.size()); - nonlinearFactors_.push_back( - boost::make_shared(factor)); - if (params_.cacheLinearizedFactors) linearFactors_.push_back(factor); + nonlinearFactorsToAdd.emplace_shared(factor); for (Key factorKey : *factor) { fixedVariables_.insert(factorKey); } } } } - variableIndex_.augment(factorsToAdd); // Augment the variable index - - // Remove the factors to remove that have been summarized in the newly-added - // marginal factors - NonlinearFactorGraph removedFactors; - for (const auto index : factorIndicesToRemove) { - removedFactors.push_back(nonlinearFactors_[index]); - nonlinearFactors_.remove(index); - if (params_.cacheLinearizedFactors) linearFactors_.remove(index); + // Add the nonlinear factors and keep track of the new factor indices + auto newFactorIndices = nonlinearFactors_.add_factors(nonlinearFactorsToAdd, + params_.findUnusedFactorSlots); + // Add cached linear factors. + if (params_.cacheLinearizedFactors){ + linearFactors_.resize(nonlinearFactors_.size()); + for (std::size_t i = 0; i < nonlinearFactorsToAdd.size(); ++i){ + linearFactors_[newFactorIndices[i]] = factorsToAdd[i]; + } } - variableIndex_.remove(factorIndicesToRemove.begin(), - factorIndicesToRemove.end(), removedFactors); - - if (deletedFactorsIndices) - deletedFactorsIndices->assign(factorIndicesToRemove.begin(), - factorIndicesToRemove.end()); + // Augment the variable index + variableIndex_.augment(factorsToAdd, newFactorIndices); // Remove the marginalized variables removeVariables(KeySet(leafKeys.begin(), leafKeys.end())); + + if (deletedFactorsIndices) { + deletedFactorsIndices->assign(factorIndicesToRemove.begin(), + factorIndicesToRemove.end()); + } + if (marginalFactorsIndices){ + *marginalFactorsIndices = std::move(newFactorIndices); + } } /* ************************************************************************* */ diff --git a/tests/testGaussianISAM2.cpp b/tests/testGaussianISAM2.cpp index 940051b96..b2a679d65 100644 --- a/tests/testGaussianISAM2.cpp +++ b/tests/testGaussianISAM2.cpp @@ -714,16 +714,17 @@ namespace { const boost::optional> orderingConstraints = createOrderingConstraints(isam, newValues.keys(), marginalizableKeys); // Mark additional keys between the marginalized keys and the leaves - KeyList additionalMarkedKeys; + KeyList markedKeys; for (Key key : marginalizableKeys) { + markedKeys.push_back(key); ISAM2Clique::shared_ptr clique = isam[key]; for (const ISAM2Clique::shared_ptr& child : clique->children) { - markAffectedKeys(key, child, additionalMarkedKeys); + markAffectedKeys(key, child, markedKeys); } } // Update - isam.update(newFactors, newValues, FactorIndices{}, orderingConstraints, boost::none, additionalMarkedKeys); + isam.update(newFactors, newValues, FactorIndices{}, orderingConstraints, boost::none, markedKeys); if (!marginalizableKeys.empty()) { FastList leafKeys(marginalizableKeys.begin(), marginalizableKeys.end()); From 242728df02ddde9604f4a54598a4facfa6c0f87f Mon Sep 17 00:00:00 2001 From: Daniel Lu Date: Tue, 18 Apr 2023 20:38:12 -0700 Subject: [PATCH 004/113] add useLOST to triangulateSafe --- gtsam/geometry/geometry.i | 2 ++ gtsam/geometry/triangulation.h | 10 +++++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/gtsam/geometry/geometry.i b/gtsam/geometry/geometry.i index e9929227a..a7fca34d1 100644 --- a/gtsam/geometry/geometry.i +++ b/gtsam/geometry/geometry.i @@ -1146,11 +1146,13 @@ class TriangulationParameters { bool enableEPI; double landmarkDistanceThreshold; double dynamicOutlierRejectionThreshold; + bool useLOST; gtsam::SharedNoiseModel noiseModel; TriangulationParameters(const double rankTolerance = 1.0, const bool enableEPI = false, double landmarkDistanceThreshold = -1, double dynamicOutlierRejectionThreshold = -1, + const bool useLOST = false, const gtsam::SharedNoiseModel& noiseModel = nullptr); }; diff --git a/gtsam/geometry/triangulation.h b/gtsam/geometry/triangulation.h index 7e58cee2d..d8578947c 100644 --- a/gtsam/geometry/triangulation.h +++ b/gtsam/geometry/triangulation.h @@ -571,6 +571,11 @@ struct GTSAM_EXPORT TriangulationParameters { */ double dynamicOutlierRejectionThreshold; + /** + * if true, will use the LOST algorithm instead of DLT + */ + bool useLOST; + SharedNoiseModel noiseModel; ///< used in the nonlinear triangulation /** @@ -585,10 +590,12 @@ struct GTSAM_EXPORT TriangulationParameters { TriangulationParameters(const double _rankTolerance = 1.0, const bool _enableEPI = false, double _landmarkDistanceThreshold = -1, double _dynamicOutlierRejectionThreshold = -1, + const bool _useLOST = false, const SharedNoiseModel& _noiseModel = nullptr) : rankTolerance(_rankTolerance), enableEPI(_enableEPI), // landmarkDistanceThreshold(_landmarkDistanceThreshold), // dynamicOutlierRejectionThreshold(_dynamicOutlierRejectionThreshold), + useLOST(_useLOST), noiseModel(_noiseModel){ } @@ -601,6 +608,7 @@ struct GTSAM_EXPORT TriangulationParameters { << std::endl; os << "dynamicOutlierRejectionThreshold = " << p.dynamicOutlierRejectionThreshold << std::endl; + os << "useLOST = " << p.useLOST << std::endl; os << "noise model" << std::endl; return os; } @@ -698,7 +706,7 @@ TriangulationResult triangulateSafe(const CameraSet& cameras, try { Point3 point = triangulatePoint3(cameras, measured, params.rankTolerance, - params.enableEPI, params.noiseModel); + params.enableEPI, params.noiseModel, params.useLOST); // Check landmark distance and re-projection errors to avoid outliers size_t i = 0; From b0404aa109eb463c0764b4edded82fd3ab8f513a Mon Sep 17 00:00:00 2001 From: Varun Agrawal Date: Tue, 20 Jun 2023 14:36:45 -0400 Subject: [PATCH 005/113] small improvements --- gtsam_unstable/linear/LPInitSolver.cpp | 4 +--- gtsam_unstable/linear/LPInitSolver.h | 3 ++- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/gtsam_unstable/linear/LPInitSolver.cpp b/gtsam_unstable/linear/LPInitSolver.cpp index 9f12d670e..3d24f784b 100644 --- a/gtsam_unstable/linear/LPInitSolver.cpp +++ b/gtsam_unstable/linear/LPInitSolver.cpp @@ -65,9 +65,7 @@ GaussianFactorGraph::shared_ptr LPInitSolver::buildInitOfInitGraph() const { new GaussianFactorGraph(lp_.equalities)); // create factor ||x||^2 and add to the graph - const KeyDimMap& constrainedKeyDim = lp_.constrainedKeyDimMap(); - for (const auto& [key, _] : constrainedKeyDim) { - size_t dim = constrainedKeyDim.at(key); + for (const auto& [key, dim] : lp_.constrainedKeyDimMap()) { initGraph->push_back( JacobianFactor(key, Matrix::Identity(dim, dim), Vector::Zero(dim))); } diff --git a/gtsam_unstable/linear/LPInitSolver.h b/gtsam_unstable/linear/LPInitSolver.h index 7e326117b..9db2a34f0 100644 --- a/gtsam_unstable/linear/LPInitSolver.h +++ b/gtsam_unstable/linear/LPInitSolver.h @@ -19,6 +19,7 @@ #pragma once +#include #include #include @@ -49,7 +50,7 @@ namespace gtsam { * inequality constraint, we can't conclude that the problem is infeasible. * However, whether it is infeasible or unbounded, we don't have a unique solution anyway. */ -class LPInitSolver { +class GTSAM_UNSTABLE_EXPORT LPInitSolver { private: const LP& lp_; From 254e3128e6d199508215ec71cb8626fdadafc45f Mon Sep 17 00:00:00 2001 From: Varun Agrawal Date: Tue, 20 Jun 2023 17:28:01 -0400 Subject: [PATCH 006/113] fix for multiply defined symbol error in LPInitSolver --- gtsam_unstable/linear/LP.h | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/gtsam_unstable/linear/LP.h b/gtsam_unstable/linear/LP.h index dbb744d3e..50065b2dd 100644 --- a/gtsam_unstable/linear/LP.h +++ b/gtsam_unstable/linear/LP.h @@ -81,9 +81,23 @@ public: if (!cachedConstrainedKeyDimMap_.empty()) return cachedConstrainedKeyDimMap_; // Collect key-dim map of all variables in the constraints - cachedConstrainedKeyDimMap_ = collectKeyDim(equalities); - KeyDimMap keysDim2 = collectKeyDim(inequalities); - cachedConstrainedKeyDimMap_.insert(keysDim2.begin(), keysDim2.end()); + //TODO(Varun) seems like the templated function is causing the multiple symbols error on Windows + // cachedConstrainedKeyDimMap_ = collectKeyDim(equalities); + // KeyDimMap keysDim2 = collectKeyDim(inequalities); + // cachedConstrainedKeyDimMap_.insert(keysDim2.begin(), keysDim2.end()); + cachedConstrainedKeyDimMap_.clear(); + for (auto&& factor : equalities) { + if (!factor) continue; + for (Key key : factor->keys()) { + cachedConstrainedKeyDimMap_[key] = factor->getDim(factor->find(key)); + } + } + for (auto&& factor : inequalities) { + if (!factor) continue; + for (Key key : factor->keys()) { + cachedConstrainedKeyDimMap_[key] = factor->getDim(factor->find(key)); + } + } return cachedConstrainedKeyDimMap_; } From 62fe1a937979968d3b8f4349e0a39aa445d13358 Mon Sep 17 00:00:00 2001 From: Varun Agrawal Date: Tue, 20 Jun 2023 17:31:49 -0400 Subject: [PATCH 007/113] add todo for error in SmartStereoProjectionFactorPP.h --- .../slam/SmartStereoProjectionFactorPP.h | 42 ++++++++++--------- 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/gtsam_unstable/slam/SmartStereoProjectionFactorPP.h b/gtsam_unstable/slam/SmartStereoProjectionFactorPP.h index f9db90953..b022ce16f 100644 --- a/gtsam_unstable/slam/SmartStereoProjectionFactorPP.h +++ b/gtsam_unstable/slam/SmartStereoProjectionFactorPP.h @@ -201,10 +201,9 @@ class GTSAM_UNSTABLE_EXPORT SmartStereoProjectionFactorPP } /// linearize and return a Hessianfactor that is an approximation of error(p) - std::shared_ptr > createHessianFactor( - const Values& values, const double lambda = 0.0, bool diagonalDamping = - false) const { - + std::shared_ptr> createHessianFactor( + const Values& values, const double lambda = 0.0, + bool diagonalDamping = false) const { // we may have multiple cameras sharing the same extrinsic cals, hence the number // of keys may be smaller than 2 * nrMeasurements (which is the upper bound where we // have a body key and an extrinsic calibration key for each measurement) @@ -212,23 +211,22 @@ class GTSAM_UNSTABLE_EXPORT SmartStereoProjectionFactorPP // Create structures for Hessian Factors KeyVector js; - std::vector < Matrix > Gs(nrUniqueKeys * (nrUniqueKeys + 1) / 2); + std::vector Gs(nrUniqueKeys * (nrUniqueKeys + 1) / 2); std::vector gs(nrUniqueKeys); if (this->measured_.size() != cameras(values).size()) throw std::runtime_error("SmartStereoProjectionHessianFactor: this->" - "measured_.size() inconsistent with input"); + "measured_.size() inconsistent with input"); // triangulate 3D point at given linearization point triangulateSafe(cameras(values)); - if (!result_) { // failed: return "empty/zero" Hessian - for (Matrix& m : Gs) - m = Matrix::Zero(DimPose, DimPose); - for (Vector& v : gs) - v = Vector::Zero(DimPose); - return std::make_shared < RegularHessianFactor - > (keys_, Gs, gs, 0.0); + // failed: return "empty/zero" Hessian + if (!result_) { + for (Matrix& m : Gs) m = Matrix::Zero(DimPose, DimPose); + for (Vector& v : gs) v = Vector::Zero(DimPose); + return std::make_shared>(keys_, Gs, gs, + 0.0); } // compute Jacobian given triangulated 3D Point @@ -239,12 +237,13 @@ class GTSAM_UNSTABLE_EXPORT SmartStereoProjectionFactorPP // Whiten using noise model noiseModel_->WhitenSystem(E, b); - for (size_t i = 0; i < Fs.size(); i++) + for (size_t i = 0; i < Fs.size(); i++) { Fs[i] = noiseModel_->Whiten(Fs[i]); + } // build augmented Hessian (with last row/column being the information vector) Matrix3 P; - Cameras::ComputePointCovariance <3> (P, E, lambda, diagonalDamping); + Cameras::ComputePointCovariance<3>(P, E, lambda, diagonalDamping); // these are the keys that correspond to the blocks in augmentedHessian (output of SchurComplement) KeyVector nonuniqueKeys; @@ -253,12 +252,15 @@ class GTSAM_UNSTABLE_EXPORT SmartStereoProjectionFactorPP nonuniqueKeys.push_back(body_P_cam_keys_.at(i)); } // but we need to get the augumented hessian wrt the unique keys in key_ - SymmetricBlockMatrix augmentedHessianUniqueKeys = - Cameras::SchurComplementAndRearrangeBlocks<3,DimBlock,DimPose>(Fs,E,P,b, - nonuniqueKeys, keys_); + //TODO(Varun) SchurComplementAndRearrangeBlocks is causing the multiply defined symbol error + // SymmetricBlockMatrix augmentedHessianUniqueKeys = + // Base::Cameras::template SchurComplementAndRearrangeBlocks<3, DimBlock, + // DimPose>( + // Fs, E, P, b, nonuniqueKeys, keys_); - return std::make_shared < RegularHessianFactor - > (keys_, augmentedHessianUniqueKeys); + // return std::make_shared>( + // keys_, augmentedHessianUniqueKeys); + return nullptr; } /** From fbf155d91e4ccbf0590fe2f693f50c7da6e528a3 Mon Sep 17 00:00:00 2001 From: Varun Agrawal Date: Wed, 21 Jun 2023 09:55:52 -0400 Subject: [PATCH 008/113] undo changes --- gtsam_unstable/linear/LP.h | 20 +++---------------- .../slam/SmartStereoProjectionFactorPP.h | 14 ++++++------- 2 files changed, 9 insertions(+), 25 deletions(-) diff --git a/gtsam_unstable/linear/LP.h b/gtsam_unstable/linear/LP.h index 50065b2dd..dbb744d3e 100644 --- a/gtsam_unstable/linear/LP.h +++ b/gtsam_unstable/linear/LP.h @@ -81,23 +81,9 @@ public: if (!cachedConstrainedKeyDimMap_.empty()) return cachedConstrainedKeyDimMap_; // Collect key-dim map of all variables in the constraints - //TODO(Varun) seems like the templated function is causing the multiple symbols error on Windows - // cachedConstrainedKeyDimMap_ = collectKeyDim(equalities); - // KeyDimMap keysDim2 = collectKeyDim(inequalities); - // cachedConstrainedKeyDimMap_.insert(keysDim2.begin(), keysDim2.end()); - cachedConstrainedKeyDimMap_.clear(); - for (auto&& factor : equalities) { - if (!factor) continue; - for (Key key : factor->keys()) { - cachedConstrainedKeyDimMap_[key] = factor->getDim(factor->find(key)); - } - } - for (auto&& factor : inequalities) { - if (!factor) continue; - for (Key key : factor->keys()) { - cachedConstrainedKeyDimMap_[key] = factor->getDim(factor->find(key)); - } - } + cachedConstrainedKeyDimMap_ = collectKeyDim(equalities); + KeyDimMap keysDim2 = collectKeyDim(inequalities); + cachedConstrainedKeyDimMap_.insert(keysDim2.begin(), keysDim2.end()); return cachedConstrainedKeyDimMap_; } diff --git a/gtsam_unstable/slam/SmartStereoProjectionFactorPP.h b/gtsam_unstable/slam/SmartStereoProjectionFactorPP.h index b022ce16f..189c501bb 100644 --- a/gtsam_unstable/slam/SmartStereoProjectionFactorPP.h +++ b/gtsam_unstable/slam/SmartStereoProjectionFactorPP.h @@ -252,15 +252,13 @@ class GTSAM_UNSTABLE_EXPORT SmartStereoProjectionFactorPP nonuniqueKeys.push_back(body_P_cam_keys_.at(i)); } // but we need to get the augumented hessian wrt the unique keys in key_ - //TODO(Varun) SchurComplementAndRearrangeBlocks is causing the multiply defined symbol error - // SymmetricBlockMatrix augmentedHessianUniqueKeys = - // Base::Cameras::template SchurComplementAndRearrangeBlocks<3, DimBlock, - // DimPose>( - // Fs, E, P, b, nonuniqueKeys, keys_); + SymmetricBlockMatrix augmentedHessianUniqueKeys = + Base::Cameras::template SchurComplementAndRearrangeBlocks<3, DimBlock, + DimPose>( + Fs, E, P, b, nonuniqueKeys, keys_); - // return std::make_shared>( - // keys_, augmentedHessianUniqueKeys); - return nullptr; + return std::make_shared>( + keys_, augmentedHessianUniqueKeys); } /** From 57311281fbea70ab639a5ad754484c55c0063a4b Mon Sep 17 00:00:00 2001 From: Varun Agrawal Date: Wed, 21 Jun 2023 09:56:16 -0400 Subject: [PATCH 009/113] use std::map for Key-Dim maps --- gtsam/geometry/CameraSet.h | 2 +- gtsam_unstable/linear/LP.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/gtsam/geometry/CameraSet.h b/gtsam/geometry/CameraSet.h index 23a4b467e..5f77ebf58 100644 --- a/gtsam/geometry/CameraSet.h +++ b/gtsam/geometry/CameraSet.h @@ -270,7 +270,7 @@ class CameraSet : public std::vector> { // Get map from key to location in the new augmented Hessian matrix (the // one including only unique keys). - std::map keyToSlotMap; + std::map keyToSlotMap; for (size_t k = 0; k < nrUniqueKeys; k++) { keyToSlotMap[hessianKeys[k]] = k; } diff --git a/gtsam_unstable/linear/LP.h b/gtsam_unstable/linear/LP.h index dbb744d3e..53247be4c 100644 --- a/gtsam_unstable/linear/LP.h +++ b/gtsam_unstable/linear/LP.h @@ -29,7 +29,7 @@ namespace gtsam { using namespace std; /// Mapping between variable's key and its corresponding dimensionality -using KeyDimMap = std::map; +using KeyDimMap = std::map; /* * Iterates through every factor in a linear graph and generates a * mapping between every factor key and it's corresponding dimensionality. From 429d5de601db382f3a5f025dc84a03ccc4e9b505 Mon Sep 17 00:00:00 2001 From: Varun Agrawal Date: Wed, 21 Jun 2023 12:30:56 -0400 Subject: [PATCH 010/113] Actions file formatting --- .github/workflows/build-windows.yml | 30 ++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/.github/workflows/build-windows.yml b/.github/workflows/build-windows.yml index 0434577c1..339f10a0a 100644 --- a/.github/workflows/build-windows.yml +++ b/.github/workflows/build-windows.yml @@ -96,23 +96,23 @@ jobs: run: | # Since Visual Studio is a multi-generator, we need to use --config # https://stackoverflow.com/a/24470998/1236990 - cmake --build build -j 4 --config ${{ matrix.build_type }} --target gtsam - cmake --build build -j 4 --config ${{ matrix.build_type }} --target gtsam_unstable - cmake --build build -j 4 --config ${{ matrix.build_type }} --target wrap + cmake --build build -j4 --config ${{ matrix.build_type }} --target gtsam + cmake --build build -j4 --config ${{ matrix.build_type }} --target gtsam_unstable + cmake --build build -j4 --config ${{ matrix.build_type }} --target wrap # Run GTSAM tests - cmake --build build -j 4 --config ${{ matrix.build_type }} --target check.base - cmake --build build -j 4 --config ${{ matrix.build_type }} --target check.basis - cmake --build build -j 4 --config ${{ matrix.build_type }} --target check.discrete - #cmake --build build -j 4 --config ${{ matrix.build_type }} --target check.geometry - cmake --build build -j 4 --config ${{ matrix.build_type }} --target check.inference - cmake --build build -j 4 --config ${{ matrix.build_type }} --target check.linear - cmake --build build -j 4 --config ${{ matrix.build_type }} --target check.navigation - #cmake --build build -j 4 --config ${{ matrix.build_type }} --target check.nonlinear - #cmake --build build -j 4 --config ${{ matrix.build_type }} --target check.sam - cmake --build build -j 4 --config ${{ matrix.build_type }} --target check.sfm - #cmake --build build -j 4 --config ${{ matrix.build_type }} --target check.slam - cmake --build build -j 4 --config ${{ matrix.build_type }} --target check.symbolic + cmake --build build -j4 --config ${{ matrix.build_type }} --target check.base + cmake --build build -j4 --config ${{ matrix.build_type }} --target check.basis + cmake --build build -j4 --config ${{ matrix.build_type }} --target check.discrete + #cmake --build build -j4 --config ${{ matrix.build_type }} --target check.geometry + cmake --build build -j4 --config ${{ matrix.build_type }} --target check.inference + cmake --build build -j4 --config ${{ matrix.build_type }} --target check.linear + cmake --build build -j4 --config ${{ matrix.build_type }} --target check.navigation + #cmake --build build -j4 --config ${{ matrix.build_type }} --target check.nonlinear + cmake --build build -j4 --config ${{ matrix.build_type }} --target check.sam + cmake --build build -j4 --config ${{ matrix.build_type }} --target check.sfm + #cmake --build build -j4 --config ${{ matrix.build_type }} --target check.slam + cmake --build build -j4 --config ${{ matrix.build_type }} --target check.symbolic # Run GTSAM_UNSTABLE tests #cmake --build build -j 4 --config ${{ matrix.build_type }} --target check.base_unstable From f65414d7efc9c8a2a7cf45d1a877815e9184983f Mon Sep 17 00:00:00 2001 From: Varun Agrawal Date: Wed, 21 Jun 2023 13:03:54 -0400 Subject: [PATCH 011/113] add GTSAM_EXPORT to additional Ordering methods --- gtsam/inference/Ordering.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/gtsam/inference/Ordering.h b/gtsam/inference/Ordering.h index 884a93f0d..0a3b25441 100644 --- a/gtsam/inference/Ordering.h +++ b/gtsam/inference/Ordering.h @@ -77,9 +77,11 @@ public: * @param keys The key vector to append to this ordering. * @return The ordering variable with appended keys. */ + GTSAM_EXPORT This& operator+=(KeyVector& keys); /// Check if key exists in ordering. + GTSAM_EXPORT bool contains(const Key& key) const; /** From 43a9fbf461ee29ec9478d21b4362d35498ee3550 Mon Sep 17 00:00:00 2001 From: Varun Agrawal Date: Wed, 21 Jun 2023 13:29:10 -0400 Subject: [PATCH 012/113] mark Pose2 as GTSAM_EXPORT at the class level --- gtsam/geometry/Pose2.h | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/gtsam/geometry/Pose2.h b/gtsam/geometry/Pose2.h index f1b38c5a6..1a62fa938 100644 --- a/gtsam/geometry/Pose2.h +++ b/gtsam/geometry/Pose2.h @@ -36,7 +36,7 @@ namespace gtsam { * @ingroup geometry * \nosubgrouping */ -class Pose2: public LieGroup { +class GTSAM_EXPORT Pose2: public LieGroup { public: @@ -112,10 +112,10 @@ public: /// @{ /** print with optional string */ - GTSAM_EXPORT void print(const std::string& s = "") const; + void print(const std::string& s = "") const; /** assert equality up to a tolerance */ - GTSAM_EXPORT bool equals(const Pose2& pose, double tol = 1e-9) const; + bool equals(const Pose2& pose, double tol = 1e-9) const; /// @} /// @name Group @@ -125,7 +125,7 @@ public: inline static Pose2 Identity() { return Pose2(); } /// inverse - GTSAM_EXPORT Pose2 inverse() const; + Pose2 inverse() const; /// compose syntactic sugar inline Pose2 operator*(const Pose2& p2) const { @@ -137,16 +137,16 @@ public: /// @{ ///Exponential map at identity - create a rotation from canonical coordinates \f$ [T_x,T_y,\theta] \f$ - GTSAM_EXPORT static Pose2 Expmap(const Vector3& xi, ChartJacobian H = {}); + static Pose2 Expmap(const Vector3& xi, ChartJacobian H = {}); ///Log map at identity - return the canonical coordinates \f$ [T_x,T_y,\theta] \f$ of this rotation - GTSAM_EXPORT static Vector3 Logmap(const Pose2& p, ChartJacobian H = {}); + static Vector3 Logmap(const Pose2& p, ChartJacobian H = {}); /** * Calculate Adjoint map * Ad_pose is 3*3 matrix that when applied to twist xi \f$ [T_x,T_y,\theta] \f$, returns Ad_pose(xi) */ - GTSAM_EXPORT Matrix3 AdjointMap() const; + Matrix3 AdjointMap() const; /// Apply AdjointMap to twist xi inline Vector3 Adjoint(const Vector3& xi) const { @@ -156,7 +156,7 @@ public: /** * Compute the [ad(w,v)] operator for SE2 as in [Kobilarov09siggraph], pg 19 */ - GTSAM_EXPORT static Matrix3 adjointMap(const Vector3& v); + static Matrix3 adjointMap(const Vector3& v); /** * Action of the adjointMap on a Lie-algebra vector y, with optional derivatives @@ -192,15 +192,15 @@ public: } /// Derivative of Expmap - GTSAM_EXPORT static Matrix3 ExpmapDerivative(const Vector3& v); + static Matrix3 ExpmapDerivative(const Vector3& v); /// Derivative of Logmap - GTSAM_EXPORT static Matrix3 LogmapDerivative(const Pose2& v); + static Matrix3 LogmapDerivative(const Pose2& v); // Chart at origin, depends on compile-time flag SLOW_BUT_CORRECT_EXPMAP struct ChartAtOrigin { - GTSAM_EXPORT static Pose2 Retract(const Vector3& v, ChartJacobian H = {}); - GTSAM_EXPORT static Vector3 Local(const Pose2& r, ChartJacobian H = {}); + static Pose2 Retract(const Vector3& v, ChartJacobian H = {}); + static Vector3 Local(const Pose2& r, ChartJacobian H = {}); }; using LieGroup::inverse; // version with derivative @@ -210,7 +210,7 @@ public: /// @{ /** Return point coordinates in pose coordinate frame */ - GTSAM_EXPORT Point2 transformTo(const Point2& point, + Point2 transformTo(const Point2& point, OptionalJacobian<2, 3> Dpose = {}, OptionalJacobian<2, 2> Dpoint = {}) const; @@ -222,7 +222,7 @@ public: Matrix transformTo(const Matrix& points) const; /** Return point coordinates in global frame */ - GTSAM_EXPORT Point2 transformFrom(const Point2& point, + Point2 transformFrom(const Point2& point, OptionalJacobian<2, 3> Dpose = {}, OptionalJacobian<2, 2> Dpoint = {}) const; @@ -273,14 +273,14 @@ public: } //// return transformation matrix - GTSAM_EXPORT Matrix3 matrix() const; + Matrix3 matrix() const; /** * Calculate bearing to a landmark * @param point 2D location of landmark * @return 2D rotation \f$ \in SO(2) \f$ */ - GTSAM_EXPORT Rot2 bearing(const Point2& point, + Rot2 bearing(const Point2& point, OptionalJacobian<1, 3> H1={}, OptionalJacobian<1, 2> H2={}) const; /** @@ -288,7 +288,7 @@ public: * @param point SO(2) location of other pose * @return 2D rotation \f$ \in SO(2) \f$ */ - GTSAM_EXPORT Rot2 bearing(const Pose2& pose, + Rot2 bearing(const Pose2& pose, OptionalJacobian<1, 3> H1={}, OptionalJacobian<1, 3> H2={}) const; /** @@ -296,7 +296,7 @@ public: * @param point 2D location of landmark * @return range (double) */ - GTSAM_EXPORT double range(const Point2& point, + double range(const Point2& point, OptionalJacobian<1, 3> H1={}, OptionalJacobian<1, 2> H2={}) const; @@ -305,7 +305,7 @@ public: * @param point 2D location of other pose * @return range (double) */ - GTSAM_EXPORT double range(const Pose2& point, + double range(const Pose2& point, OptionalJacobian<1, 3> H1={}, OptionalJacobian<1, 3> H2={}) const; From c9397c34fcd9c287c905fa091dc254a5d0f054ac Mon Sep 17 00:00:00 2001 From: Varun Agrawal Date: Wed, 21 Jun 2023 13:29:32 -0400 Subject: [PATCH 013/113] CameraSet::project2 SFINAE to resolve overload ambiguity --- gtsam/geometry/CameraSet.h | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/gtsam/geometry/CameraSet.h b/gtsam/geometry/CameraSet.h index 5f77ebf58..8621d77ad 100644 --- a/gtsam/geometry/CameraSet.h +++ b/gtsam/geometry/CameraSet.h @@ -131,12 +131,15 @@ class CameraSet : public std::vector> { return z; } - /** An overload o the project2 function to accept + /** An overload of the project2 function to accept * full matrices and vectors and pass it to the pointer - * version of the function + * version of the function. + * + * Use SFINAE to resolve overload ambiguity. */ template - ZVector project2(const POINT& point, OptArgs&... args) const { + typename std::enable_if<(sizeof...(OptArgs) != 0), ZVector>::type project2( + const POINT& point, OptArgs&... args) const { // pass it to the pointer version of the function return project2(point, (&args)...); } From 5fa889b035c238b95c84de1e7e04cc50a275edcb Mon Sep 17 00:00:00 2001 From: Varun Agrawal Date: Wed, 21 Jun 2023 17:32:26 -0400 Subject: [PATCH 014/113] add GTSAM_EXPORT tags --- gtsam/discrete/SignatureParser.h | 2 +- gtsam_unstable/linear/QPSParser.h | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/gtsam/discrete/SignatureParser.h b/gtsam/discrete/SignatureParser.h index e6b402e44..e005da14a 100644 --- a/gtsam/discrete/SignatureParser.h +++ b/gtsam/discrete/SignatureParser.h @@ -47,7 +47,7 @@ namespace gtsam { * * Also fails if the rows are not of the same size. */ -struct SignatureParser { +struct GTSAM_EXPORT SignatureParser { using Row = std::vector; using Table = std::vector; diff --git a/gtsam_unstable/linear/QPSParser.h b/gtsam_unstable/linear/QPSParser.h index bd4254d0f..e751f34d2 100644 --- a/gtsam_unstable/linear/QPSParser.h +++ b/gtsam_unstable/linear/QPSParser.h @@ -18,12 +18,13 @@ #pragma once +#include #include #include namespace gtsam { -class QPSParser { +class GTSAM_UNSTABLE_EXPORT QPSParser { private: std::string fileName_; From c954e546ef4fb6614344d417a76ad922df45af29 Mon Sep 17 00:00:00 2001 From: Varun Agrawal Date: Wed, 21 Jun 2023 17:33:14 -0400 Subject: [PATCH 015/113] enable more Windows tests --- .github/workflows/build-windows.yml | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build-windows.yml b/.github/workflows/build-windows.yml index 339f10a0a..e85064d08 100644 --- a/.github/workflows/build-windows.yml +++ b/.github/workflows/build-windows.yml @@ -104,15 +104,23 @@ jobs: cmake --build build -j4 --config ${{ matrix.build_type }} --target check.base cmake --build build -j4 --config ${{ matrix.build_type }} --target check.basis cmake --build build -j4 --config ${{ matrix.build_type }} --target check.discrete - #cmake --build build -j4 --config ${{ matrix.build_type }} --target check.geometry + cmake --build build -j4 --config ${{ matrix.build_type }} --target check.geometry cmake --build build -j4 --config ${{ matrix.build_type }} --target check.inference cmake --build build -j4 --config ${{ matrix.build_type }} --target check.linear cmake --build build -j4 --config ${{ matrix.build_type }} --target check.navigation - #cmake --build build -j4 --config ${{ matrix.build_type }} --target check.nonlinear cmake --build build -j4 --config ${{ matrix.build_type }} --target check.sam cmake --build build -j4 --config ${{ matrix.build_type }} --target check.sfm - #cmake --build build -j4 --config ${{ matrix.build_type }} --target check.slam cmake --build build -j4 --config ${{ matrix.build_type }} --target check.symbolic + # cmake --build build -j4 --config ${{ matrix.build_type }} --target check.hybrid + # cmake --build build -j4 --config ${{ matrix.build_type }} --target check.nonlinear + # cmake --build build -j4 --config ${{ matrix.build_type }} --target check.slam # Run GTSAM_UNSTABLE tests - #cmake --build build -j 4 --config ${{ matrix.build_type }} --target check.base_unstable + cmake --build build -j4 --config ${{ matrix.build_type }} --target check.base_unstable + # cmake --build build -j4 --config ${{ matrix.build_type }} --target check.geometry_unstable + # cmake --build build -j4 --config ${{ matrix.build_type }} --target check.linear_unstable + # cmake --build build -j4 --config ${{ matrix.build_type }} --target check.discrete_unstable + # cmake --build build -j4 --config ${{ matrix.build_type }} --target check.dynamics_unstable + # cmake --build build -j4 --config ${{ matrix.build_type }} --target check.nonlinear_unstable + # cmake --build build -j4 --config ${{ matrix.build_type }} --target check.slam_unstable + # cmake --build build -j4 --config ${{ matrix.build_type }} --target check.partition From 0bc08b88bc7598eb5d8f6554a113235d3dd8a012 Mon Sep 17 00:00:00 2001 From: Varun Agrawal Date: Wed, 21 Jun 2023 17:49:15 -0400 Subject: [PATCH 016/113] remove GTSAM_EXPORT from SignatureParser struct --- gtsam/discrete/SignatureParser.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gtsam/discrete/SignatureParser.h b/gtsam/discrete/SignatureParser.h index e005da14a..e6b402e44 100644 --- a/gtsam/discrete/SignatureParser.h +++ b/gtsam/discrete/SignatureParser.h @@ -47,7 +47,7 @@ namespace gtsam { * * Also fails if the rows are not of the same size. */ -struct GTSAM_EXPORT SignatureParser { +struct SignatureParser { using Row = std::vector; using Table = std::vector; From 2168d0f0867640b7eb2610e8a294a9bb4a04fa45 Mon Sep 17 00:00:00 2001 From: Tal Regev Date: Fri, 30 Jun 2023 19:43:34 +0300 Subject: [PATCH 017/113] Compile with ninja --- .github/workflows/build-windows.yml | 31 +++++++++++++++++------------ 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/.github/workflows/build-windows.yml b/.github/workflows/build-windows.yml index 0434577c1..9326a81b0 100644 --- a/.github/workflows/build-windows.yml +++ b/.github/workflows/build-windows.yml @@ -2,6 +2,10 @@ name: Windows CI on: [pull_request] +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + jobs: build: name: ${{ matrix.name }} ${{ matrix.build_type }} @@ -16,19 +20,15 @@ jobs: BOOST_EXE: boost_1_72_0-msvc-14.2 strategy: - fail-fast: true + fail-fast: false matrix: - # Github Actions requires a single row to be added to the build matrix. - # See https://help.github.com/en/articles/workflow-syntax-for-github-actions. - name: [ - windows-2019-cl, - ] - - build_type: [ - Debug, - Release - ] - build_unstable: [ON] + build_type: + - Debug + # - Release + + build_unstable: + - ON + # - OFF include: - name: windows-2019-cl os: windows-2019 @@ -87,10 +87,15 @@ jobs: - name: Checkout uses: actions/checkout@v3 + - name: Setup msbuild + uses: ilammy/msvc-dev-cmd@v1 + with: + arch: x${{ matrix.platform }} + - name: Configuration run: | cmake -E remove_directory build - cmake -B build -S . -DGTSAM_BUILD_EXAMPLES_ALWAYS=OFF -DBOOST_ROOT="${env:BOOST_ROOT}" -DBOOST_INCLUDEDIR="${env:BOOST_ROOT}\boost\include" -DBOOST_LIBRARYDIR="${env:BOOST_ROOT}\lib" + cmake -G Ninja -B build -S . -DGTSAM_BUILD_EXAMPLES_ALWAYS=OFF -DBOOST_ROOT="${env:BOOST_ROOT}" -DBOOST_INCLUDEDIR="${env:BOOST_ROOT}\boost\include" -DBOOST_LIBRARYDIR="${env:BOOST_ROOT}\lib" - name: Build run: | From 92de2273a86bd316205b018d425449d1ca4d4a0e Mon Sep 17 00:00:00 2001 From: Tal Regev Date: Fri, 30 Jun 2023 20:21:11 +0300 Subject: [PATCH 018/113] Fix linkage errors: unresolved external symbol --- gtsam/basis/Basis.h | 2 +- gtsam/discrete/SignatureParser.h | 2 +- gtsam/geometry/Pose2.h | 6 +++--- gtsam/geometry/Pose3.h | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/gtsam/basis/Basis.h b/gtsam/basis/Basis.h index 41cdeeaaa..0b2b3606b 100644 --- a/gtsam/basis/Basis.h +++ b/gtsam/basis/Basis.h @@ -80,7 +80,7 @@ using Weights = Eigen::Matrix; /* 1xN vector */ * * @ingroup basis */ -Matrix kroneckerProductIdentity(size_t M, const Weights& w); +Matrix GTSAM_EXPORT kroneckerProductIdentity(size_t M, const Weights& w); /** * CRTP Base class for function bases diff --git a/gtsam/discrete/SignatureParser.h b/gtsam/discrete/SignatureParser.h index e6b402e44..e005da14a 100644 --- a/gtsam/discrete/SignatureParser.h +++ b/gtsam/discrete/SignatureParser.h @@ -47,7 +47,7 @@ namespace gtsam { * * Also fails if the rows are not of the same size. */ -struct SignatureParser { +struct GTSAM_EXPORT SignatureParser { using Row = std::vector; using Table = std::vector; diff --git a/gtsam/geometry/Pose2.h b/gtsam/geometry/Pose2.h index f1b38c5a6..b455a81c3 100644 --- a/gtsam/geometry/Pose2.h +++ b/gtsam/geometry/Pose2.h @@ -198,9 +198,9 @@ public: GTSAM_EXPORT static Matrix3 LogmapDerivative(const Pose2& v); // Chart at origin, depends on compile-time flag SLOW_BUT_CORRECT_EXPMAP - struct ChartAtOrigin { - GTSAM_EXPORT static Pose2 Retract(const Vector3& v, ChartJacobian H = {}); - GTSAM_EXPORT static Vector3 Local(const Pose2& r, ChartJacobian H = {}); + struct GTSAM_EXPORT ChartAtOrigin { + static Pose2 Retract(const Vector3& v, ChartJacobian H = {}); + static Vector3 Local(const Pose2& r, ChartJacobian H = {}); }; using LieGroup::inverse; // version with derivative diff --git a/gtsam/geometry/Pose3.h b/gtsam/geometry/Pose3.h index 6c91d7468..8a807cc23 100644 --- a/gtsam/geometry/Pose3.h +++ b/gtsam/geometry/Pose3.h @@ -204,7 +204,7 @@ public: static Matrix6 LogmapDerivative(const Pose3& xi); // Chart at origin, depends on compile-time flag GTSAM_POSE3_EXPMAP - struct ChartAtOrigin { + struct GTSAM_EXPORT ChartAtOrigin { static Pose3 Retract(const Vector6& xi, ChartJacobian Hxi = {}); static Vector6 Local(const Pose3& pose, ChartJacobian Hpose = {}); }; From a499855eafe945f5559d4712054bd48289c3699d Mon Sep 17 00:00:00 2001 From: Tal Regev Date: Fri, 30 Jun 2023 21:56:24 +0300 Subject: [PATCH 019/113] Add release to windows tests --- .github/workflows/build-windows.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-windows.yml b/.github/workflows/build-windows.yml index 9326a81b0..dabdff7f9 100644 --- a/.github/workflows/build-windows.yml +++ b/.github/workflows/build-windows.yml @@ -24,7 +24,7 @@ jobs: matrix: build_type: - Debug - # - Release + - Release build_unstable: - ON From 9aa67b5235f8cb11a6bad8ab08f422f6fe78a7c4 Mon Sep 17 00:00:00 2001 From: Tal Regev Date: Fri, 30 Jun 2023 22:14:43 +0300 Subject: [PATCH 020/113] Add #include --- gtsam/discrete/SignatureParser.h | 1 + 1 file changed, 1 insertion(+) diff --git a/gtsam/discrete/SignatureParser.h b/gtsam/discrete/SignatureParser.h index e005da14a..fec3b9af2 100644 --- a/gtsam/discrete/SignatureParser.h +++ b/gtsam/discrete/SignatureParser.h @@ -21,6 +21,7 @@ #include #include #include +#include namespace gtsam { /** From 7f46117666cfb35f7a4e90a8ef6cf72b14f0b17c Mon Sep 17 00:00:00 2001 From: Tal Regev Date: Fri, 30 Jun 2023 22:37:43 +0300 Subject: [PATCH 021/113] Add non concurrency to all workflows --- .github/workflows/build-linux.yml | 6 ++++++ .github/workflows/build-macos.yml | 6 ++++++ .github/workflows/build-python.yml | 6 ++++++ .github/workflows/build-special.yml | 6 ++++++ .github/workflows/build-windows.yml | 2 ++ 5 files changed, 26 insertions(+) diff --git a/.github/workflows/build-linux.yml b/.github/workflows/build-linux.yml index 129b5aaaf..e4937ce06 100644 --- a/.github/workflows/build-linux.yml +++ b/.github/workflows/build-linux.yml @@ -2,6 +2,12 @@ name: Linux CI on: [pull_request] +# Every time you make a push to your PR, it cancel immediately the previous checks, +# and start a new one. The other runner will be available more quickly to your PR. +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + jobs: build: name: ${{ matrix.name }} ${{ matrix.build_type }} diff --git a/.github/workflows/build-macos.yml b/.github/workflows/build-macos.yml index 3fa3c15dd..541701836 100644 --- a/.github/workflows/build-macos.yml +++ b/.github/workflows/build-macos.yml @@ -2,6 +2,12 @@ name: macOS CI on: [pull_request] +# Every time you make a push to your PR, it cancel immediately the previous checks, +# and start a new one. The other runner will be available more quickly to your PR. +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + jobs: build: name: ${{ matrix.name }} ${{ matrix.build_type }} diff --git a/.github/workflows/build-python.yml b/.github/workflows/build-python.yml index f09d589dc..ca4645a77 100644 --- a/.github/workflows/build-python.yml +++ b/.github/workflows/build-python.yml @@ -2,6 +2,12 @@ name: Python CI on: [pull_request] +# Every time you make a push to your PR, it cancel immediately the previous checks, +# and start a new one. The other runner will be available more quickly to your PR. +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + jobs: build: name: ${{ matrix.name }} ${{ matrix.build_type }} Python ${{ matrix.python_version }} diff --git a/.github/workflows/build-special.yml b/.github/workflows/build-special.yml index f72dadbae..72466ffd6 100644 --- a/.github/workflows/build-special.yml +++ b/.github/workflows/build-special.yml @@ -2,6 +2,12 @@ name: Special Cases CI on: [pull_request] +# Every time you make a push to your PR, it cancel immediately the previous checks, +# and start a new one. The other runner will be available more quickly to your PR. +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + jobs: build: name: ${{ matrix.name }} ${{ matrix.build_type }} diff --git a/.github/workflows/build-windows.yml b/.github/workflows/build-windows.yml index 99b654b3a..5d5342f3f 100644 --- a/.github/workflows/build-windows.yml +++ b/.github/workflows/build-windows.yml @@ -2,6 +2,8 @@ name: Windows CI on: [pull_request] +# Every time you make a push to your PR, it cancel immediately the previous checks, +# and start a new one. The other runner will be available more quickly to your PR. concurrency: group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} cancel-in-progress: true From d1bd76a0aadb06da0ff258e8091b1e8ba9c7b10d Mon Sep 17 00:00:00 2001 From: Tal Regev Date: Sat, 1 Jul 2023 17:02:03 +0300 Subject: [PATCH 022/113] Check more tests --- .github/workflows/build-windows.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build-windows.yml b/.github/workflows/build-windows.yml index 5d5342f3f..7f8c56558 100644 --- a/.github/workflows/build-windows.yml +++ b/.github/workflows/build-windows.yml @@ -118,9 +118,9 @@ jobs: cmake --build build -j4 --config ${{ matrix.build_type }} --target check.sam cmake --build build -j4 --config ${{ matrix.build_type }} --target check.sfm cmake --build build -j4 --config ${{ matrix.build_type }} --target check.symbolic - # cmake --build build -j4 --config ${{ matrix.build_type }} --target check.hybrid - # cmake --build build -j4 --config ${{ matrix.build_type }} --target check.nonlinear - # cmake --build build -j4 --config ${{ matrix.build_type }} --target check.slam + cmake --build build -j4 --config ${{ matrix.build_type }} --target check.hybrid + cmake --build build -j4 --config ${{ matrix.build_type }} --target check.nonlinear + cmake --build build -j4 --config ${{ matrix.build_type }} --target check.slam # Run GTSAM_UNSTABLE tests cmake --build build -j4 --config ${{ matrix.build_type }} --target check.base_unstable From ac86415032ff54a3b1b488e4dc7060796675baf3 Mon Sep 17 00:00:00 2001 From: Tal Regev Date: Sat, 1 Jul 2023 21:20:56 +0300 Subject: [PATCH 023/113] Fix tests --- .github/workflows/build-windows.yml | 14 +++++++------- gtsam/hybrid/HybridFactorGraph.h | 2 +- gtsam/hybrid/HybridSmoother.h | 2 +- .../hybrid/tests/testHybridGaussianFactorGraph.cpp | 1 + 4 files changed, 10 insertions(+), 9 deletions(-) diff --git a/.github/workflows/build-windows.yml b/.github/workflows/build-windows.yml index 7f8c56558..e0f56aad2 100644 --- a/.github/workflows/build-windows.yml +++ b/.github/workflows/build-windows.yml @@ -123,11 +123,11 @@ jobs: cmake --build build -j4 --config ${{ matrix.build_type }} --target check.slam # Run GTSAM_UNSTABLE tests + cmake --build build -j4 --config ${{ matrix.build_type }} --target check.geometry_unstable + cmake --build build -j4 --config ${{ matrix.build_type }} --target check.linear_unstable + cmake --build build -j4 --config ${{ matrix.build_type }} --target check.discrete_unstable + cmake --build build -j4 --config ${{ matrix.build_type }} --target check.dynamics_unstable + cmake --build build -j4 --config ${{ matrix.build_type }} --target check.nonlinear_unstable + cmake --build build -j4 --config ${{ matrix.build_type }} --target check.slam_unstable + cmake --build build -j4 --config ${{ matrix.build_type }} --target check.partition cmake --build build -j4 --config ${{ matrix.build_type }} --target check.base_unstable - # cmake --build build -j4 --config ${{ matrix.build_type }} --target check.geometry_unstable - # cmake --build build -j4 --config ${{ matrix.build_type }} --target check.linear_unstable - # cmake --build build -j4 --config ${{ matrix.build_type }} --target check.discrete_unstable - # cmake --build build -j4 --config ${{ matrix.build_type }} --target check.dynamics_unstable - # cmake --build build -j4 --config ${{ matrix.build_type }} --target check.nonlinear_unstable - # cmake --build build -j4 --config ${{ matrix.build_type }} --target check.slam_unstable - # cmake --build build -j4 --config ${{ matrix.build_type }} --target check.partition diff --git a/gtsam/hybrid/HybridFactorGraph.h b/gtsam/hybrid/HybridFactorGraph.h index b8e7ff588..8b59fd4f9 100644 --- a/gtsam/hybrid/HybridFactorGraph.h +++ b/gtsam/hybrid/HybridFactorGraph.h @@ -35,7 +35,7 @@ using SharedFactor = std::shared_ptr; * Hybrid Factor Graph * Factor graph with utilities for hybrid factors. */ -class HybridFactorGraph : public FactorGraph { +class GTSAM_EXPORT HybridFactorGraph : public FactorGraph { public: using Base = FactorGraph; using This = HybridFactorGraph; ///< this class diff --git a/gtsam/hybrid/HybridSmoother.h b/gtsam/hybrid/HybridSmoother.h index 0767da12f..5c2e4f546 100644 --- a/gtsam/hybrid/HybridSmoother.h +++ b/gtsam/hybrid/HybridSmoother.h @@ -24,7 +24,7 @@ namespace gtsam { -class HybridSmoother { +class GTSAM_EXPORT HybridSmoother { private: HybridBayesNet hybridBayesNet_; HybridGaussianFactorGraph remainingFactorGraph_; diff --git a/gtsam/hybrid/tests/testHybridGaussianFactorGraph.cpp b/gtsam/hybrid/tests/testHybridGaussianFactorGraph.cpp index 8276264ae..073f49c04 100644 --- a/gtsam/hybrid/tests/testHybridGaussianFactorGraph.cpp +++ b/gtsam/hybrid/tests/testHybridGaussianFactorGraph.cpp @@ -43,6 +43,7 @@ #include #include #include +#include #include "Switching.h" #include "TinyHybridExample.h" From e00e1236b5799c63d9c8a756d3e742a3c47caa67 Mon Sep 17 00:00:00 2001 From: Varun Agrawal Date: Mon, 3 Jul 2023 08:56:08 -0400 Subject: [PATCH 024/113] reorganize CI workflow file --- .github/workflows/build-windows.yml | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/.github/workflows/build-windows.yml b/.github/workflows/build-windows.yml index e0f56aad2..7123d6eea 100644 --- a/.github/workflows/build-windows.yml +++ b/.github/workflows/build-windows.yml @@ -22,15 +22,20 @@ jobs: BOOST_EXE: boost_1_72_0-msvc-14.2 strategy: - fail-fast: false + fail-fast: true matrix: - build_type: - - Debug - - Release - - build_unstable: - - ON - # - OFF + # Github Actions requires a single row to be added to the build matrix. + # See https://help.github.com/en/articles/workflow-syntax-for-github-actions. + name: [ + windows-2019-cl, + ] + + build_type: [ + Debug, + Release + ] + + build_unstable: [ON] include: - name: windows-2019-cl os: windows-2019 @@ -123,6 +128,7 @@ jobs: cmake --build build -j4 --config ${{ matrix.build_type }} --target check.slam # Run GTSAM_UNSTABLE tests + cmake --build build -j4 --config ${{ matrix.build_type }} --target check.base_unstable cmake --build build -j4 --config ${{ matrix.build_type }} --target check.geometry_unstable cmake --build build -j4 --config ${{ matrix.build_type }} --target check.linear_unstable cmake --build build -j4 --config ${{ matrix.build_type }} --target check.discrete_unstable @@ -130,4 +136,3 @@ jobs: cmake --build build -j4 --config ${{ matrix.build_type }} --target check.nonlinear_unstable cmake --build build -j4 --config ${{ matrix.build_type }} --target check.slam_unstable cmake --build build -j4 --config ${{ matrix.build_type }} --target check.partition - cmake --build build -j4 --config ${{ matrix.build_type }} --target check.base_unstable From 02ecc80ecfae7f26752e469de7cc8e17c469d790 Mon Sep 17 00:00:00 2001 From: Varun Agrawal Date: Mon, 24 Jul 2023 20:46:30 -0400 Subject: [PATCH 025/113] additional ordering test --- gtsam/inference/tests/testOrdering.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/gtsam/inference/tests/testOrdering.cpp b/gtsam/inference/tests/testOrdering.cpp index 328d383d8..b6cfcb6ed 100644 --- a/gtsam/inference/tests/testOrdering.cpp +++ b/gtsam/inference/tests/testOrdering.cpp @@ -219,6 +219,11 @@ TEST(Ordering, AppendVector) { Ordering expected{X(0), X(1), X(2)}; EXPECT(assert_equal(expected, actual)); + + actual = Ordering(); + Ordering addl{X(0), X(1), X(2)}; + actual += addl; + EXPECT(assert_equal(expected, actual)); } /* ************************************************************************* */ From 5f93febcbe0d9f98d9fce4b13a498e14f10ae4df Mon Sep 17 00:00:00 2001 From: Varun Agrawal Date: Tue, 25 Jul 2023 11:35:09 -0400 Subject: [PATCH 026/113] keyformatter for NonlinearFactorGraph::printErrors in python --- gtsam/nonlinear/nonlinear.i | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/gtsam/nonlinear/nonlinear.i b/gtsam/nonlinear/nonlinear.i index 06451ab1f..3f5fb1dd5 100644 --- a/gtsam/nonlinear/nonlinear.i +++ b/gtsam/nonlinear/nonlinear.i @@ -86,7 +86,10 @@ class NonlinearFactorGraph { const gtsam::noiseModel::Base* noiseModel); // NonlinearFactorGraph - void printErrors(const gtsam::Values& values) const; + void printErrors(const gtsam::Values& values, + const string& str = "NonlinearFactorGraph: ", + const gtsam::KeyFormatter& keyFormatter = + gtsam::DefaultKeyFormatter) const; double error(const gtsam::Values& values) const; double probPrime(const gtsam::Values& values) const; gtsam::Ordering orderingCOLAMD() const; From b51ff749645d2b6ecc8b3d4ed8434e7a2276caaa Mon Sep 17 00:00:00 2001 From: Varun Agrawal Date: Tue, 25 Jul 2023 14:28:14 -0400 Subject: [PATCH 027/113] discrete conditional from vector of doubles --- gtsam/discrete/DiscreteConditional.h | 12 ++++++++++++ gtsam/discrete/DiscreteKey.h | 6 ++++++ gtsam/discrete/tests/testDiscreteConditional.cpp | 5 +++++ 3 files changed, 23 insertions(+) diff --git a/gtsam/discrete/DiscreteConditional.h b/gtsam/discrete/DiscreteConditional.h index 183cf8561..50fa6e161 100644 --- a/gtsam/discrete/DiscreteConditional.h +++ b/gtsam/discrete/DiscreteConditional.h @@ -77,6 +77,18 @@ class GTSAM_EXPORT DiscreteConditional const Signature::Table& table) : DiscreteConditional(Signature(key, parents, table)) {} + /** + * Construct from key, parents, and a vector specifying the + * conditional probability table (CPT) in 00 01 10 11 order. For + * three-valued, it would be 00 01 02 10 11 12 20 21 22, etc.... + * + * Example: DiscreteConditional P(D, {B,E}, table); + */ + DiscreteConditional(const DiscreteKey& key, const DiscreteKeys& parents, + const std::vector& table) + : DiscreteConditional(1, DiscreteKeys{key} & parents, + ADT(DiscreteKeys{key} & parents, table)) {} + /** * Construct from key, parents, and a string specifying the conditional * probability table (CPT) in 00 01 10 11 order. For three-valued, it would diff --git a/gtsam/discrete/DiscreteKey.h b/gtsam/discrete/DiscreteKey.h index 3a626c6b3..44cc192ef 100644 --- a/gtsam/discrete/DiscreteKey.h +++ b/gtsam/discrete/DiscreteKey.h @@ -74,6 +74,12 @@ namespace gtsam { return *this; } + /// Add multiple keys (non-const!) + DiscreteKeys& operator&(const DiscreteKeys& keys) { + this->insert(this->end(), keys.begin(), keys.end()); + return *this; + } + /// Print the keys and cardinalities. void print(const std::string& s = "", const KeyFormatter& keyFormatter = DefaultKeyFormatter) const; diff --git a/gtsam/discrete/tests/testDiscreteConditional.cpp b/gtsam/discrete/tests/testDiscreteConditional.cpp index aa393d74c..9439f5653 100644 --- a/gtsam/discrete/tests/testDiscreteConditional.cpp +++ b/gtsam/discrete/tests/testDiscreteConditional.cpp @@ -46,6 +46,11 @@ TEST(DiscreteConditional, constructors) { DiscreteConditional actual2(1, f2); DecisionTreeFactor expected2 = f2 / *f2.sum(1); EXPECT(assert_equal(expected2, static_cast(actual2))); + + std::vector probs{0.2, 0.5, 0.3, 0.6, 0.4, 0.7, 0.25, 0.55, 0.35, 0.65, 0.45, 0.75}; + DiscreteConditional actual3(X, {Y, Z}, probs); + DecisionTreeFactor expected3 = f2; + EXPECT(assert_equal(expected3, static_cast(actual3))); } /* ************************************************************************* */ From 38e52810bc288ccf070cc308fd74e618267e1a98 Mon Sep 17 00:00:00 2001 From: Varun Agrawal Date: Thu, 27 Jul 2023 14:45:08 -0400 Subject: [PATCH 028/113] remove extra GTSAM_EXPORTs from Ordering.h --- gtsam/inference/Ordering.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/gtsam/inference/Ordering.h b/gtsam/inference/Ordering.h index 5202a2749..c3df1b8a0 100644 --- a/gtsam/inference/Ordering.h +++ b/gtsam/inference/Ordering.h @@ -70,11 +70,9 @@ public: * @param keys The key vector to append to this ordering. * @return The ordering variable with appended keys. */ - GTSAM_EXPORT This& operator+=(KeyVector& keys); /// Check if key exists in ordering. - GTSAM_EXPORT bool contains(const Key& key) const; /** From d473cef470b4fd5ca1bbbd0b239d0ca39df46872 Mon Sep 17 00:00:00 2001 From: Fan Jiang Date: Fri, 28 Jul 2023 14:13:38 -0700 Subject: [PATCH 029/113] Add new loss functions --- gtsam/linear/LossFunctions.cpp | 93 ++++++++++++++++++++++++++++++++++ gtsam/linear/LossFunctions.h | 74 +++++++++++++++++++++++++++ gtsam/linear/linear.i | 13 +++++ 3 files changed, 180 insertions(+) diff --git a/gtsam/linear/LossFunctions.cpp b/gtsam/linear/LossFunctions.cpp index 64ded7cc3..8099ca81e 100644 --- a/gtsam/linear/LossFunctions.cpp +++ b/gtsam/linear/LossFunctions.cpp @@ -424,6 +424,99 @@ L2WithDeadZone::shared_ptr L2WithDeadZone::Create(double k, const ReweightScheme return shared_ptr(new L2WithDeadZone(k, reweight)); } + +/* ************************************************************************* */ +// AsymmetricTukey +/* ************************************************************************* */ + +AsymmetricTukey::AsymmetricTukey(double c, const ReweightScheme reweight) : Base(reweight), c_(c), csquared_(c * c) { + if (c <= 0) { + throw runtime_error("mEstimator AsymmetricTukey takes only positive double in constructor."); + } +} + +double AsymmetricTukey::weight(double distance) const { + distance = -distance; + if (distance >= 0.0) { + return distance; + } else if (distance > -c_) { + const double one_minus_xc2 = 1.0 - distance * distance / csquared_; + return one_minus_xc2 * one_minus_xc2; + } + return 0.0; +} + +double AsymmetricTukey::loss(double distance) const { + distance = -distance; + if (distance >= 0.0) { + return distance * distance / 2.0; + } else if (distance >= -c_) { + const double one_minus_xc2 = 1.0 - distance * distance / csquared_; + const double t = one_minus_xc2 * one_minus_xc2 * one_minus_xc2; + return csquared_ * (1 - t) / 6.0; + } + return csquared_ / 6.0; +} + +void AsymmetricTukey::print(const std::string &s="") const { + std::cout << s << ": AsymmetricTukey (" << c_ << ")" << std::endl; +} + +bool AsymmetricTukey::equals(const Base &expected, double tol) const { + const AsymmetricTukey* p = dynamic_cast(&expected); + if (p == nullptr) return false; + return std::abs(c_ - p->c_) < tol; +} + +AsymmetricTukey::shared_ptr AsymmetricTukey::Create(double c, const ReweightScheme reweight) { + return shared_ptr(new AsymmetricTukey(c, reweight)); +} + + +/* ************************************************************************* */ +// AsymmetricCauchy +/* ************************************************************************* */ + +AsymmetricCauchy::AsymmetricCauchy(double k, const ReweightScheme reweight) : Base(reweight), k_(k), ksquared_(k * k) { + if (k <= 0) { + throw runtime_error("mEstimator AsymmetricCauchy takes only positive double in constructor."); + } +} + +double AsymmetricCauchy::weight(double distance) const { + distance = -distance; + if (distance >= 0.0) { + return distance; + } + + return ksquared_ / (ksquared_ + distance*distance); + +} + +double AsymmetricCauchy::loss(double distance) const { + distance = -distance; + if (distance >= 0.0) { + return distance * distance / 2.0; + } + const double val = std::log1p(distance * distance / ksquared_); + return ksquared_ * val * 0.5; +} + +void AsymmetricCauchy::print(const std::string &s="") const { + std::cout << s << ": AsymmetricCauchy (" << k_ << ")" << std::endl; +} + +bool AsymmetricCauchy::equals(const Base &expected, double tol) const { + const AsymmetricCauchy* p = dynamic_cast(&expected); + if (p == nullptr) return false; + return std::abs(k_ - p->k_) < tol; +} + +AsymmetricCauchy::shared_ptr AsymmetricCauchy::Create(double k, const ReweightScheme reweight) { + return shared_ptr(new AsymmetricCauchy(k, reweight)); +} + + } // namespace mEstimator } // namespace noiseModel } // gtsam diff --git a/gtsam/linear/LossFunctions.h b/gtsam/linear/LossFunctions.h index 70166d97e..1e28755bb 100644 --- a/gtsam/linear/LossFunctions.h +++ b/gtsam/linear/LossFunctions.h @@ -15,6 +15,7 @@ * @date Jan 13, 2010 * @author Richard Roberts * @author Frank Dellaert + * @author Fan Jiang */ #pragma once @@ -470,6 +471,79 @@ class GTSAM_EXPORT L2WithDeadZone : public Base { #endif }; +/** Implementation of the "AsymmetricTukey" robust error model. + * + * This model has a scalar parameter "c". + * + * - Following are all for one side, the other is standard L2 + * - Loss \rho(x) = c² (1 - (1-x²/c²)³)/6 if |x| shared_ptr; + + AsymmetricTukey(double c = 4.6851, const ReweightScheme reweight = Block); + double weight(double distance) const override; + double loss(double distance) const override; + void print(const std::string &s) const override; + bool equals(const Base &expected, double tol = 1e-8) const override; + static shared_ptr Create(double k, const ReweightScheme reweight = Block); + double modelParameter() const { return c_; } + + private: +#ifdef GTSAM_ENABLE_BOOST_SERIALIZATION + /** Serialization function */ + friend class boost::serialization::access; + template + void serialize(ARCHIVE &ar, const unsigned int /*version*/) { + ar &BOOST_SERIALIZATION_BASE_OBJECT_NVP(Base); + ar &BOOST_SERIALIZATION_NVP(c_); + } +#endif +}; + +/** Implementation of the "AsymmetricCauchy" robust error model. + * + * This model has a scalar parameter "k". + * + * - Following are all for one side, the other is standard L2 + * - Loss \rho(x) = 0.5 k² log(1+x²/k²) + * - Derivative \phi(x) = (k²x)/(x²+k²) + * - Weight w(x) = \phi(x)/x = k²/(x²+k²) + */ +class GTSAM_EXPORT AsymmetricCauchy : public Base { + protected: + double k_, ksquared_; + + public: + typedef std::shared_ptr shared_ptr; + + AsymmetricCauchy(double k = 0.1, const ReweightScheme reweight = Block); + double weight(double distance) const override; + double loss(double distance) const override; + void print(const std::string &s) const override; + bool equals(const Base &expected, double tol = 1e-8) const override; + static shared_ptr Create(double k, const ReweightScheme reweight = Block); + double modelParameter() const { return k_; } + + private: +#ifdef GTSAM_ENABLE_BOOST_SERIALIZATION + /** Serialization function */ + friend class boost::serialization::access; + template + void serialize(ARCHIVE &ar, const unsigned int /*version*/) { + ar &BOOST_SERIALIZATION_BASE_OBJECT_NVP(Base); + ar &BOOST_SERIALIZATION_NVP(k_); + ar &BOOST_SERIALIZATION_NVP(ksquared_); + } +#endif +}; + } // namespace mEstimator } // namespace noiseModel } // namespace gtsam diff --git a/gtsam/linear/linear.i b/gtsam/linear/linear.i index c0230f1c2..63359ae2b 100644 --- a/gtsam/linear/linear.i +++ b/gtsam/linear/linear.i @@ -89,6 +89,7 @@ virtual class Unit : gtsam::noiseModel::Isotropic { namespace mEstimator { virtual class Base { + enum ReweightScheme { Scalar, Block }; void print(string s = "") const; }; @@ -191,6 +192,18 @@ virtual class L2WithDeadZone: gtsam::noiseModel::mEstimator::Base { double loss(double error) const; }; +virtual class AsymmetricTukey: gtsam::noiseModel::mEstimator::Base { + AsymmetricTukey(double k, gtsam::noiseModel::mEstimator::Base::ReweightScheme reweight); + static gtsam::noiseModel::mEstimator::AsymmetricTukey* Create(double k); + + // enabling serialization functionality + void serializable() const; + + double weight(double error) const; + double loss(double error) const; +}; + + }///\namespace mEstimator virtual class Robust : gtsam::noiseModel::Base { From e562d708a3db88e68341b1a0c17ce811ddbe4a8f Mon Sep 17 00:00:00 2001 From: Varun Agrawal Date: Sun, 30 Jul 2023 10:16:15 -0400 Subject: [PATCH 030/113] enable more tests --- .github/workflows/build-windows.yml | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/.github/workflows/build-windows.yml b/.github/workflows/build-windows.yml index af629c7be..9b54cb94b 100644 --- a/.github/workflows/build-windows.yml +++ b/.github/workflows/build-windows.yml @@ -102,7 +102,7 @@ jobs: - name: Configuration run: | cmake -E remove_directory build - cmake -G Ninja -B build -S . -DGTSAM_BUILD_EXAMPLES_ALWAYS=OFF -DBOOST_ROOT="${env:BOOST_ROOT}" -DBOOST_INCLUDEDIR="${env:BOOST_ROOT}\boost\include" -DBOOST_LIBRARYDIR="${env:BOOST_ROOT}\lib" + cmake -G Ninja -B build -S . -DGTSAM_BUILD_EXAMPLES_ALWAYS=OFF -DGTSAM_ALLOW_DEPRECATED_SINCE_V43=OFF -DBOOST_ROOT="${env:BOOST_ROOT}" -DBOOST_INCLUDEDIR="${env:BOOST_ROOT}\boost\include" -DBOOST_LIBRARYDIR="${env:BOOST_ROOT}\lib" - name: Build shell: bash @@ -112,9 +112,6 @@ jobs: cmake --build build -j4 --config ${{ matrix.build_type }} --target gtsam cmake --build build -j4 --config ${{ matrix.build_type }} --target gtsam_unstable - # Target doesn't exist - # cmake --build build -j4 --config ${{ matrix.build_type }} --target wrap - - name: Test shell: bash run: | @@ -122,19 +119,15 @@ jobs: cmake --build build -j4 --config ${{ matrix.build_type }} --target check.base cmake --build build -j4 --config ${{ matrix.build_type }} --target check.basis cmake --build build -j4 --config ${{ matrix.build_type }} --target check.discrete - # Compilation error - # cmake --build build -j4 --config ${{ matrix.build_type }} --target check.geometry + cmake --build build -j4 --config ${{ matrix.build_type }} --target check.geometry cmake --build build -j4 --config ${{ matrix.build_type }} --target check.inference - # Compile. Fail with exception cmake --build build -j4 --config ${{ matrix.build_type }} --target check.linear cmake --build build -j4 --config ${{ matrix.build_type }} --target check.navigation cmake --build build -j4 --config ${{ matrix.build_type }} --target check.sam cmake --build build -j4 --config ${{ matrix.build_type }} --target check.sfm cmake --build build -j4 --config ${{ matrix.build_type }} --target check.symbolic - # Compile. Fail with exception cmake --build build -j4 --config ${{ matrix.build_type }} --target check.hybrid - # Compile. Fail with exception - # cmake --build build -j4 --config ${{ matrix.build_type }} --target check.nonlinear + cmake --build build -j4 --config ${{ matrix.build_type }} --target check.nonlinear # Compilation error # cmake --build build -j4 --config ${{ matrix.build_type }} --target check.slam From 67b49d6a5cc57eec93b827a752522b349122b331 Mon Sep 17 00:00:00 2001 From: Varun Agrawal Date: Sun, 30 Jul 2023 13:57:00 -0400 Subject: [PATCH 031/113] fix tests in testValues --- gtsam/nonlinear/tests/testValues.cpp | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/gtsam/nonlinear/tests/testValues.cpp b/gtsam/nonlinear/tests/testValues.cpp index 2a7bede30..a2b265a56 100644 --- a/gtsam/nonlinear/tests/testValues.cpp +++ b/gtsam/nonlinear/tests/testValues.cpp @@ -581,7 +581,7 @@ TEST(Values, std_move) { TEST(Values, VectorDynamicInsertFixedRead) { Values values; Vector v(3); v << 5.0, 6.0, 7.0; - values.insert(key1, v); + values.insert(key1, v); Vector3 expected(5.0, 6.0, 7.0); Vector3 actual = values.at(key1); CHECK(assert_equal(expected, actual)); @@ -629,7 +629,7 @@ TEST(Values, VectorFixedInsertFixedRead) { TEST(Values, MatrixDynamicInsertFixedRead) { Values values; Matrix v(1,3); v << 5.0, 6.0, 7.0; - values.insert(key1, v); + values.insert(key1, v); Vector3 expected(5.0, 6.0, 7.0); CHECK(assert_equal((Vector)expected, values.at(key1))); CHECK_EXCEPTION(values.at(key1), exception); @@ -639,7 +639,15 @@ TEST(Values, Demangle) { Values values; Matrix13 v; v << 5.0, 6.0, 7.0; values.insert(key1, v); - string expected = "Values with 1 values:\nValue v1: (Eigen::Matrix)\n[\n 5, 6, 7\n]\n\n"; +#ifdef __GNUG__ + string expected = + "Values with 1 values:\nValue v1: (Eigen::Matrix)\n[\n 5, 6, 7\n]\n\n"; +#elif _WIN32 + string expected = + "Values with 1 values:\nValue v1: " + "(class Eigen::Matrix)\n[\n 5, 6, 7\n]\n\n"; +#endif EXPECT(assert_print_equal(expected, values)); } From df3899e2e8bfc9e98dad4e493409ae11e17d1989 Mon Sep 17 00:00:00 2001 From: Varun Agrawal Date: Sun, 30 Jul 2023 14:11:05 -0400 Subject: [PATCH 032/113] comment out nonlinear check --- .github/workflows/build-windows.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-windows.yml b/.github/workflows/build-windows.yml index 9b54cb94b..3a2d3816a 100644 --- a/.github/workflows/build-windows.yml +++ b/.github/workflows/build-windows.yml @@ -127,8 +127,8 @@ jobs: cmake --build build -j4 --config ${{ matrix.build_type }} --target check.sfm cmake --build build -j4 --config ${{ matrix.build_type }} --target check.symbolic cmake --build build -j4 --config ${{ matrix.build_type }} --target check.hybrid - cmake --build build -j4 --config ${{ matrix.build_type }} --target check.nonlinear - # Compilation error + # Compilation error or test failure + # cmake --build build -j4 --config ${{ matrix.build_type }} --target check.nonlinear # cmake --build build -j4 --config ${{ matrix.build_type }} --target check.slam # Run GTSAM_UNSTABLE tests From 7f7acd70125035a144f88402c2a09ce5e64c4748 Mon Sep 17 00:00:00 2001 From: Fan Jiang Date: Mon, 31 Jul 2023 10:19:41 -0700 Subject: [PATCH 033/113] Add unit test --- gtsam/linear/tests/testNoiseModel.cpp | 33 +++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/gtsam/linear/tests/testNoiseModel.cpp b/gtsam/linear/tests/testNoiseModel.cpp index f1830f37f..7d2c283a6 100644 --- a/gtsam/linear/tests/testNoiseModel.cpp +++ b/gtsam/linear/tests/testNoiseModel.cpp @@ -14,6 +14,7 @@ * @date Jan 13, 2010 * @author Richard Roberts * @author Frank Dellaert + * @author Fan Jiang */ @@ -504,6 +505,22 @@ TEST(NoiseModel, robustFunctionCauchy) DOUBLES_EQUAL(0.490258914416017, cauchy->loss(error4), 1e-8); } +TEST(NoiseModel, robustFunctionAsymmetricCauchy) +{ + const double k = 5.0, error1 = 1.0, error2 = 10.0, error3 = -10.0, error4 = -1.0; + const mEstimator::AsymmetricCauchy::shared_ptr cauchy = mEstimator::AsymmetricCauchy::Create(k); + DOUBLES_EQUAL(0.961538461538461, cauchy->weight(error1), 1e-8); + DOUBLES_EQUAL(0.2000, cauchy->weight(error2), 1e-8); + // Test negative value to ensure we take absolute value of error. + DOUBLES_EQUAL(10.0, cauchy->weight(error3), 1e-8); + DOUBLES_EQUAL(1.0, cauchy->weight(error4), 1e-8); + + DOUBLES_EQUAL(0.490258914416017, cauchy->loss(error1), 1e-8); + DOUBLES_EQUAL(20.117973905426254, cauchy->loss(error2), 1e-8); + DOUBLES_EQUAL(50.0, cauchy->loss(error3), 1e-8); + DOUBLES_EQUAL(0.5, cauchy->loss(error4), 1e-8); +} + TEST(NoiseModel, robustFunctionGemanMcClure) { const double k = 1.0, error1 = 1.0, error2 = 10.0, error3 = -10.0, error4 = -1.0; @@ -551,6 +568,22 @@ TEST(NoiseModel, robustFunctionTukey) DOUBLES_EQUAL(0.480266666666667, tukey->loss(error4), 1e-8); } +TEST(NoiseModel, robustFunctionAsymmetricTukey) +{ + const double k = 5.0, error1 = 1.0, error2 = 10.0, error3 = -10.0, error4 = -1.0; + const mEstimator::AsymmetricTukey::shared_ptr tukey = mEstimator::AsymmetricTukey::Create(k); + DOUBLES_EQUAL(0.9216, tukey->weight(error1), 1e-8); + DOUBLES_EQUAL(0.0, tukey->weight(error2), 1e-8); + // Test negative value to ensure we take absolute value of error. + DOUBLES_EQUAL(10.0, tukey->weight(error3), 1e-8); + DOUBLES_EQUAL(1.0, tukey->weight(error4), 1e-8); + + DOUBLES_EQUAL(0.480266666666667, tukey->loss(error1), 1e-8); + DOUBLES_EQUAL(4.166666666666667, tukey->loss(error2), 1e-8); + DOUBLES_EQUAL(50.0, tukey->loss(error3), 1e-8); + DOUBLES_EQUAL(0.5, tukey->loss(error4), 1e-8); +} + TEST(NoiseModel, robustFunctionDCS) { const double k = 1.0, error1 = 1.0, error2 = 10.0; From ddef6445699835557f269a5c52b700d1269d095d Mon Sep 17 00:00:00 2001 From: Varun Agrawal Date: Mon, 31 Jul 2023 16:58:06 -0400 Subject: [PATCH 034/113] fix nonlinear tests --- .github/workflows/build-windows.yml | 2 +- gtsam/nonlinear/ISAM2.h | 2 +- gtsam/nonlinear/tests/testSerializationNonlinear.cpp | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build-windows.yml b/.github/workflows/build-windows.yml index 3a2d3816a..357c4ca08 100644 --- a/.github/workflows/build-windows.yml +++ b/.github/workflows/build-windows.yml @@ -127,8 +127,8 @@ jobs: cmake --build build -j4 --config ${{ matrix.build_type }} --target check.sfm cmake --build build -j4 --config ${{ matrix.build_type }} --target check.symbolic cmake --build build -j4 --config ${{ matrix.build_type }} --target check.hybrid + cmake --build build -j4 --config ${{ matrix.build_type }} --target check.nonlinear # Compilation error or test failure - # cmake --build build -j4 --config ${{ matrix.build_type }} --target check.nonlinear # cmake --build build -j4 --config ${{ matrix.build_type }} --target check.slam # Run GTSAM_UNSTABLE tests diff --git a/gtsam/nonlinear/ISAM2.h b/gtsam/nonlinear/ISAM2.h index 50c5058c2..d3abd95fd 100644 --- a/gtsam/nonlinear/ISAM2.h +++ b/gtsam/nonlinear/ISAM2.h @@ -345,7 +345,7 @@ class GTSAM_EXPORT ISAM2 : public BayesTree { friend class boost::serialization::access; template void serialize(ARCHIVE & ar, const unsigned int /*version*/) { - ar & boost::serialization::base_object >(*this); + ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(Base); ar & BOOST_SERIALIZATION_NVP(theta_); ar & BOOST_SERIALIZATION_NVP(variableIndex_); ar & BOOST_SERIALIZATION_NVP(delta_); diff --git a/gtsam/nonlinear/tests/testSerializationNonlinear.cpp b/gtsam/nonlinear/tests/testSerializationNonlinear.cpp index f402656c1..d6f693a23 100644 --- a/gtsam/nonlinear/tests/testSerializationNonlinear.cpp +++ b/gtsam/nonlinear/tests/testSerializationNonlinear.cpp @@ -183,7 +183,7 @@ TEST(Serialization, ISAM2) { std::string binaryPath = "saved_solver.dat"; try { - std::ofstream outputStream(binaryPath); + std::ofstream outputStream(binaryPath, std::ios::out | std::ios::binary); boost::archive::binary_oarchive outputArchive(outputStream); outputArchive << solver; } catch(...) { @@ -192,7 +192,7 @@ TEST(Serialization, ISAM2) { gtsam::ISAM2 solverFromDisk; try { - std::ifstream ifs(binaryPath); + std::ifstream ifs(binaryPath, std::ios::in | std::ios::binary); boost::archive::binary_iarchive inputArchive(ifs); inputArchive >> solverFromDisk; } catch(...) { From ced6c53e06e291618a80d039710f234d34593288 Mon Sep 17 00:00:00 2001 From: Varun Agrawal Date: Mon, 31 Jul 2023 23:06:11 -0400 Subject: [PATCH 035/113] add GTSAM_EXPORT to various ostream operator overloads --- gtsam/geometry/PinholePose.h | 3 ++- gtsam/geometry/Similarity2.h | 3 ++- gtsam/geometry/Similarity3.h | 3 ++- gtsam/geometry/Unit3.h | 3 ++- gtsam/geometry/triangulation.h | 4 ++-- gtsam/linear/IterativeSolver.h | 4 ++-- 6 files changed, 12 insertions(+), 8 deletions(-) diff --git a/gtsam/geometry/PinholePose.h b/gtsam/geometry/PinholePose.h index 5cad3b6e7..df6ec5c08 100644 --- a/gtsam/geometry/PinholePose.h +++ b/gtsam/geometry/PinholePose.h @@ -332,7 +332,8 @@ public: } /// stream operator - friend std::ostream& operator<<(std::ostream &os, const PinholePose& camera) { + GTSAM_EXPORT friend std::ostream& operator<<(std::ostream& os, + const PinholePose& camera) { os << "{R: " << camera.pose().rotation().rpy().transpose(); os << ", t: " << camera.pose().translation().transpose(); if (!camera.K_) os << ", K: none"; diff --git a/gtsam/geometry/Similarity2.h b/gtsam/geometry/Similarity2.h index 47281b383..3e04aa3a1 100644 --- a/gtsam/geometry/Similarity2.h +++ b/gtsam/geometry/Similarity2.h @@ -76,7 +76,8 @@ class GTSAM_EXPORT Similarity2 : public LieGroup { /// Print with optional string void print(const std::string& s) const; - friend std::ostream& operator<<(std::ostream& os, const Similarity2& p); + GTSAM_EXPORT friend std::ostream& operator<<(std::ostream& os, + const Similarity2& p); /// @} /// @name Group diff --git a/gtsam/geometry/Similarity3.h b/gtsam/geometry/Similarity3.h index aa0f3a62a..69b401620 100644 --- a/gtsam/geometry/Similarity3.h +++ b/gtsam/geometry/Similarity3.h @@ -77,7 +77,8 @@ class GTSAM_EXPORT Similarity3 : public LieGroup { /// Print with optional string void print(const std::string& s) const; - friend std::ostream& operator<<(std::ostream& os, const Similarity3& p); + GTSAM_EXPORT friend std::ostream& operator<<(std::ostream& os, + const Similarity3& p); /// @} /// @name Group diff --git a/gtsam/geometry/Unit3.h b/gtsam/geometry/Unit3.h index 72dd49c29..18bc5d9f0 100644 --- a/gtsam/geometry/Unit3.h +++ b/gtsam/geometry/Unit3.h @@ -105,7 +105,8 @@ public: /// @name Testable /// @{ - friend std::ostream& operator<<(std::ostream& os, const Unit3& pair); + GTSAM_EXPORT friend std::ostream& operator<<(std::ostream& os, + const Unit3& pair); /// The print fuction void print(const std::string& s = std::string()) const; diff --git a/gtsam/geometry/triangulation.h b/gtsam/geometry/triangulation.h index 3a8398804..68795a646 100644 --- a/gtsam/geometry/triangulation.h +++ b/gtsam/geometry/triangulation.h @@ -665,8 +665,8 @@ class TriangulationResult : public std::optional { return value(); } // stream to output - friend std::ostream& operator<<(std::ostream& os, - const TriangulationResult& result) { + GTSAM_EXPORT friend std::ostream& operator<<( + std::ostream& os, const TriangulationResult& result) { if (result) os << "point = " << *result << std::endl; else diff --git a/gtsam/linear/IterativeSolver.h b/gtsam/linear/IterativeSolver.h index c4a719436..0441cd9da 100644 --- a/gtsam/linear/IterativeSolver.h +++ b/gtsam/linear/IterativeSolver.h @@ -72,8 +72,8 @@ public: GTSAM_EXPORT virtual void print(std::ostream &os) const; /* for serialization */ - friend std::ostream& operator<<(std::ostream &os, - const IterativeOptimizationParameters &p); + GTSAM_EXPORT friend std::ostream &operator<<( + std::ostream &os, const IterativeOptimizationParameters &p); GTSAM_EXPORT static Verbosity verbosityTranslator(const std::string &s); GTSAM_EXPORT static std::string verbosityTranslator(Verbosity v); From c8184eb109877e40a503f09d842d88732896014c Mon Sep 17 00:00:00 2001 From: Varun Agrawal Date: Tue, 1 Aug 2023 09:55:00 -0400 Subject: [PATCH 036/113] fix SphericalCamera traits definition --- gtsam/geometry/SphericalCamera.h | 4 ++-- gtsam/slam/SmartFactorBase.h | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/gtsam/geometry/SphericalCamera.h b/gtsam/geometry/SphericalCamera.h index a9c38cd69..ef20aa7fe 100644 --- a/gtsam/geometry/SphericalCamera.h +++ b/gtsam/geometry/SphericalCamera.h @@ -238,9 +238,9 @@ class GTSAM_EXPORT SphericalCamera { // end of class SphericalCamera template <> -struct traits : public internal::LieGroup {}; +struct traits : public internal::Manifold {}; template <> -struct traits : public internal::LieGroup {}; +struct traits : public internal::Manifold {}; } // namespace gtsam diff --git a/gtsam/slam/SmartFactorBase.h b/gtsam/slam/SmartFactorBase.h index e0540cc41..5d6ec4445 100644 --- a/gtsam/slam/SmartFactorBase.h +++ b/gtsam/slam/SmartFactorBase.h @@ -162,8 +162,9 @@ protected: /// Collect all cameras: important that in key order. virtual Cameras cameras(const Values& values) const { Cameras cameras; - for(const Key& k: this->keys_) + for(const Key& k: this->keys_) { cameras.push_back(values.at(k)); + } return cameras; } From 2c569c1fdd58492d4afbab91abc34dc555d530b6 Mon Sep 17 00:00:00 2001 From: Varun Agrawal Date: Tue, 1 Aug 2023 09:59:43 -0400 Subject: [PATCH 037/113] enable slam tests --- .github/workflows/build-windows.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/build-windows.yml b/.github/workflows/build-windows.yml index 357c4ca08..670d87b3f 100644 --- a/.github/workflows/build-windows.yml +++ b/.github/workflows/build-windows.yml @@ -128,8 +128,7 @@ jobs: cmake --build build -j4 --config ${{ matrix.build_type }} --target check.symbolic cmake --build build -j4 --config ${{ matrix.build_type }} --target check.hybrid cmake --build build -j4 --config ${{ matrix.build_type }} --target check.nonlinear - # Compilation error or test failure - # cmake --build build -j4 --config ${{ matrix.build_type }} --target check.slam + cmake --build build -j4 --config ${{ matrix.build_type }} --target check.slam # Run GTSAM_UNSTABLE tests cmake --build build -j4 --config ${{ matrix.build_type }} --target check.base_unstable From 8b4fbb1d7bdfd1b6e5c09c061f66e850d065bc0e Mon Sep 17 00:00:00 2001 From: Varun Agrawal Date: Tue, 1 Aug 2023 10:54:48 -0400 Subject: [PATCH 038/113] address PR comments --- gtsam/discrete/SignatureParser.h | 1 - gtsam/geometry/CameraSet.h | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/gtsam/discrete/SignatureParser.h b/gtsam/discrete/SignatureParser.h index 5de8b9a50..ddc548fc6 100644 --- a/gtsam/discrete/SignatureParser.h +++ b/gtsam/discrete/SignatureParser.h @@ -21,7 +21,6 @@ #include #include #include -#include #include diff --git a/gtsam/geometry/CameraSet.h b/gtsam/geometry/CameraSet.h index 8621d77ad..950747102 100644 --- a/gtsam/geometry/CameraSet.h +++ b/gtsam/geometry/CameraSet.h @@ -273,7 +273,7 @@ class CameraSet : public std::vector> { // Get map from key to location in the new augmented Hessian matrix (the // one including only unique keys). - std::map keyToSlotMap; + std::map keyToSlotMap; for (size_t k = 0; k < nrUniqueKeys; k++) { keyToSlotMap[hessianKeys[k]] = k; } From 88ab398b2ab626146d238923a927de5fa15ab80b Mon Sep 17 00:00:00 2001 From: Tal Regev Date: Thu, 3 Aug 2023 21:16:52 +0300 Subject: [PATCH 039/113] Simplify scripts --- .github/scripts/python.sh | 21 +++++++++---------- .github/scripts/unix.sh | 44 +++++++++++++++++++-------------------- 2 files changed, 31 insertions(+), 34 deletions(-) diff --git a/.github/scripts/python.sh b/.github/scripts/python.sh index d026aa123..de073759b 100644 --- a/.github/scripts/python.sh +++ b/.github/scripts/python.sh @@ -9,14 +9,13 @@ set -x -e # install TBB with _debug.so files function install_tbb() { + echo install_tbb if [ "$(uname)" == "Linux" ]; then sudo apt-get -y install libtbb-dev elif [ "$(uname)" == "Darwin" ]; then brew install tbb - fi - } if [ -z ${PYTHON_VERSION+x} ]; then @@ -37,19 +36,19 @@ function install_dependencies() export PATH=$PATH:$($PYTHON -c "import site; print(site.USER_BASE)")/bin - [ "${GTSAM_WITH_TBB:-OFF}" = "ON" ] && install_tbb + if [ "${GTSAM_WITH_TBB:-OFF}" == "ON" ]; then + install_tbb + fi $PYTHON -m pip install -r $GITHUB_WORKSPACE/python/requirements.txt } function build() { - mkdir $GITHUB_WORKSPACE/build - cd $GITHUB_WORKSPACE/build - BUILD_PYBIND="ON" - - cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} \ + cmake $GITHUB_WORKSPACE \ + -B build \ + -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} \ -DGTSAM_BUILD_TESTS=OFF \ -DGTSAM_BUILD_UNSTABLE=${GTSAM_BUILD_UNSTABLE:-ON} \ -DGTSAM_USE_QUATERNIONS=OFF \ @@ -65,16 +64,16 @@ function build() # Set to 2 cores so that Actions does not error out during resource provisioning. - make -j2 install + cmake --build build -j2 - cd $GITHUB_WORKSPACE/build/python - $PYTHON -m pip install --user . + $PYTHON -m pip install --user build/python } function test() { cd $GITHUB_WORKSPACE/python/gtsam/tests $PYTHON -m unittest discover -v + cd $GITHUB_WORKSPACE } # select between build or test diff --git a/.github/scripts/unix.sh b/.github/scripts/unix.sh index 557255474..05d969d40 100644 --- a/.github/scripts/unix.sh +++ b/.github/scripts/unix.sh @@ -5,33 +5,30 @@ # Specifically Linux and macOS. ########################################################## +set -e # Make sure any error makes the script to return an error code +set -x # echo + # install TBB with _debug.so files function install_tbb() { + echo install_tbb if [ "$(uname)" == "Linux" ]; then sudo apt-get -y install libtbb-dev elif [ "$(uname)" == "Darwin" ]; then brew install tbb - fi - } # common tasks before either build or test function configure() { - set -e # Make sure any error makes the script to return an error code - set -x # echo + # delete old build + rm -rf build - SOURCE_DIR=$GITHUB_WORKSPACE - BUILD_DIR=$GITHUB_WORKSPACE/build - - #env - rm -fr $BUILD_DIR || true - mkdir $BUILD_DIR && cd $BUILD_DIR - - [ "${GTSAM_WITH_TBB:-OFF}" = "ON" ] && install_tbb + if [ "${GTSAM_WITH_TBB:-OFF}" == "ON" ]; then + install_tbb + fi if [ ! -z "$GCC_VERSION" ]; then export CC=gcc-$GCC_VERSION @@ -40,7 +37,8 @@ function configure() # GTSAM_BUILD_WITH_MARCH_NATIVE=OFF: to avoid crashes in builder VMs # CMAKE_CXX_FLAGS="-w": Suppress warnings to avoid IO latency in CI logs - cmake $SOURCE_DIR \ + cmake $GITHUB_WORKSPACE \ + -B build \ -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE:-Debug} \ -DCMAKE_CXX_FLAGS="-w" \ -DGTSAM_BUILD_TESTS=${GTSAM_BUILD_TESTS:-OFF} \ @@ -62,9 +60,9 @@ function configure() function finish () { # Print ccache stats - [ -x "$(command -v ccache)" ] && ccache -s - - cd $SOURCE_DIR + if [ -x "$(command -v ccache)" ]; then + ccache -s + fi } # compile the code with the intent of populating the cache @@ -77,12 +75,12 @@ function build () if [ "$(uname)" == "Linux" ]; then if (($(nproc) > 2)); then - make -j4 + cmake --build build -j4 else - make -j2 + cmake --build build -j2 fi elif [ "$(uname)" == "Darwin" ]; then - make -j$(sysctl -n hw.physicalcpu) + cmake --build build -j$(sysctl -n hw.physicalcpu) fi finish @@ -99,12 +97,12 @@ function test () # Actual testing if [ "$(uname)" == "Linux" ]; then if (($(nproc) > 2)); then - make -j$(nproc) check + cmake --build build -j$(nproc) --target check else - make -j2 check + cmake --build build -j2 --target check fi elif [ "$(uname)" == "Darwin" ]; then - make -j$(sysctl -n hw.physicalcpu) check + cmake --build build -j$(sysctl -n hw.physicalcpu) --target check fi finish @@ -118,4 +116,4 @@ case $1 in -t) test ;; -esac \ No newline at end of file +esac From 44d948e98e95bee9e67d8735ef29214d00ef866e Mon Sep 17 00:00:00 2001 From: Fan Jiang Date: Mon, 7 Aug 2023 23:31:58 -0700 Subject: [PATCH 040/113] Remove the boost references Signed-off-by: Fan Jiang --- tests/testGaussianISAM2.cpp | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/tests/testGaussianISAM2.cpp b/tests/testGaussianISAM2.cpp index e78d36f0e..1ea60dac9 100644 --- a/tests/testGaussianISAM2.cpp +++ b/tests/testGaussianISAM2.cpp @@ -659,10 +659,10 @@ namespace { return ok; } - boost::optional> createOrderingConstraints(const ISAM2& isam, const KeyVector& newKeys, const KeySet& marginalizableKeys) + std::optional> createOrderingConstraints(const ISAM2& isam, const KeyVector& newKeys, const KeySet& marginalizableKeys) { if (marginalizableKeys.empty()) { - return boost::none; + return {}; } else { FastMap constrainedKeys = FastMap(); // Generate ordering constraints so that the marginalizable variables will be eliminated first @@ -706,7 +706,7 @@ namespace { bool updateAndMarginalize(const NonlinearFactorGraph& newFactors, const Values& newValues, const KeySet& marginalizableKeys, ISAM2& isam) { // Force ISAM2 to put marginalizable variables at the beginning - const boost::optional> orderingConstraints = createOrderingConstraints(isam, newValues.keys(), marginalizableKeys); + auto orderingConstraints = createOrderingConstraints(isam, newValues.keys(), marginalizableKeys); // Mark additional keys between the marginalized keys and the leaves KeyList markedKeys; @@ -719,7 +719,7 @@ namespace { } // Update - isam.update(newFactors, newValues, FactorIndices{}, orderingConstraints, boost::none, markedKeys); + isam.update(newFactors, newValues, FactorIndices{}, orderingConstraints, {}, markedKeys); if (!marginalizableKeys.empty()) { FastList leafKeys(marginalizableKeys.begin(), marginalizableKeys.end()); @@ -865,7 +865,7 @@ TEST(ISAM2, marginalizeLeaves5) /* ************************************************************************* */ TEST(ISAM2, marginalizeLeaves6) { - const boost::shared_ptr nm = noiseModel::Isotropic::Sigma(6, 1.0); + auto nm = noiseModel::Isotropic::Sigma(6, 1.0); int gridDim = 10; @@ -878,16 +878,16 @@ TEST(ISAM2, marginalizeLeaves6) // Create a grid of pose variables for (int i = 0; i < gridDim; ++i) { for (int j = 0; j < gridDim; ++j) { - Pose3 pose = Pose3(Rot3::identity(), Point3(i, j, 0)); + Pose3 pose = Pose3(Rot3::Identity(), Point3(i, j, 0)); Key key = idxToKey(i, j); // Place a prior on the first pose - factors.addPrior(key, Pose3(Rot3::identity(), Point3(i, j, 0)), nm); + factors.addPrior(key, Pose3(Rot3::Identity(), Point3(i, j, 0)), nm); values.insert(key, pose); if (i > 0) { - factors.emplace_shared>(idxToKey(i - 1, j), key, Pose3(Rot3::identity(), Point3(1, 0, 0)),nm); + factors.emplace_shared>(idxToKey(i - 1, j), key, Pose3(Rot3::Identity(), Point3(1, 0, 0)),nm); } if (j > 0) { - factors.emplace_shared>(idxToKey(i, j - 1), key, Pose3(Rot3::identity(), Point3(0, 1, 0)),nm); + factors.emplace_shared>(idxToKey(i, j - 1), key, Pose3(Rot3::Identity(), Point3(0, 1, 0)),nm); } } } @@ -914,17 +914,17 @@ TEST(ISAM2, marginalizeLeaves6) /* ************************************************************************* */ TEST(ISAM2, MarginalizeRoot) { - const boost::shared_ptr nm = noiseModel::Isotropic::Sigma(6, 1.0); + auto nm = noiseModel::Isotropic::Sigma(6, 1.0); NonlinearFactorGraph factors; Values values; ISAM2 isam; // Create a factor graph with one variable and a prior - Pose3 root = Pose3::identity(); + Pose3 root = Pose3::Identity(); Key rootKey(0); values.insert(rootKey, root); - factors.addPrior(rootKey, Pose3::identity(), nm); + factors.addPrior(rootKey, Pose3::Identity(), nm); // Optimize the graph EXPECT(updateAndMarginalize(factors, values, {}, isam)); @@ -944,7 +944,7 @@ TEST(ISAM2, MarginalizeRoot) /* ************************************************************************* */ TEST(ISAM2, marginalizationSize) { - const boost::shared_ptr nm = noiseModel::Isotropic::Sigma(6, 1.0); + auto nm = noiseModel::Isotropic::Sigma(6, 1.0); NonlinearFactorGraph factors; Values values; @@ -954,13 +954,13 @@ TEST(ISAM2, marginalizationSize) // Create a pose variable Key aKey(0); - values.insert(aKey, Pose3::identity()); - factors.addPrior(aKey, Pose3::identity(), nm); + values.insert(aKey, Pose3::Identity()); + factors.addPrior(aKey, Pose3::Identity(), nm); // Create another pose variable linked to the first - Pose3 b = Pose3::identity(); + Pose3 b = Pose3::Identity(); Key bKey(1); - values.insert(bKey, Pose3::identity()); - factors.emplace_shared>(aKey, bKey, Pose3::identity(), nm); + values.insert(bKey, Pose3::Identity()); + factors.emplace_shared>(aKey, bKey, Pose3::Identity(), nm); // Optimize the graph EXPECT(updateAndMarginalize(factors, values, {}, isam)); From 9c40a2cd8b54b298e8d7e0bd28fbf40bbd6851f1 Mon Sep 17 00:00:00 2001 From: Fan Jiang Date: Fri, 18 Aug 2023 17:09:47 -0400 Subject: [PATCH 041/113] Add a custom loss function for Robust, fix a bug in Asymmetric Signed-off-by: Fan Jiang --- gtsam/linear/LossFunctions.cpp | 38 +++++++++++++++++++++-- gtsam/linear/LossFunctions.h | 44 +++++++++++++++++++++++++++ gtsam/linear/linear.i | 18 +++++++++++ gtsam/linear/tests/testNoiseModel.cpp | 39 ++++++++++++++++++++++-- 4 files changed, 135 insertions(+), 4 deletions(-) diff --git a/gtsam/linear/LossFunctions.cpp b/gtsam/linear/LossFunctions.cpp index 8099ca81e..682783e7c 100644 --- a/gtsam/linear/LossFunctions.cpp +++ b/gtsam/linear/LossFunctions.cpp @@ -19,6 +19,7 @@ #include #include +#include #include using namespace std; @@ -438,7 +439,7 @@ AsymmetricTukey::AsymmetricTukey(double c, const ReweightScheme reweight) : Base double AsymmetricTukey::weight(double distance) const { distance = -distance; if (distance >= 0.0) { - return distance; + return 1.0; } else if (distance > -c_) { const double one_minus_xc2 = 1.0 - distance * distance / csquared_; return one_minus_xc2 * one_minus_xc2; @@ -486,7 +487,7 @@ AsymmetricCauchy::AsymmetricCauchy(double k, const ReweightScheme reweight) : Ba double AsymmetricCauchy::weight(double distance) const { distance = -distance; if (distance >= 0.0) { - return distance; + return 1.0; } return ksquared_ / (ksquared_ + distance*distance); @@ -517,6 +518,39 @@ AsymmetricCauchy::shared_ptr AsymmetricCauchy::Create(double k, const ReweightSc } +/* ************************************************************************* */ +// Custom +/* ************************************************************************* */ + +Custom::Custom(std::function weight, std::function loss, const ReweightScheme reweight, + std::string name) + : Base(reweight), weight_(std::move(weight)), loss_(loss), name_(std::move(name)) {} + +double Custom::weight(double distance) const { + return weight_(distance); +} + +double Custom::loss(double distance) const { + return loss_(distance); +} + +void Custom::print(const std::string &s = "") const { + std::cout << s << ": Custom (" << name_ << ")" << std::endl; +} + +bool Custom::equals(const Base &expected, double tol) const { + const auto *p = dynamic_cast(&expected); + if (p == nullptr) + return false; + return name_ == p->name_ && weight_.target() == p->weight_.target() && + loss_.target() == p->loss_.target() && reweight_ == p->reweight_; +} + +Custom::shared_ptr Custom::Create(std::function weight, std::function loss, + const ReweightScheme reweight, const std::string &name) { + return std::make_shared(std::move(weight), std::move(loss), reweight, name); +} + } // namespace mEstimator } // namespace noiseModel } // gtsam diff --git a/gtsam/linear/LossFunctions.h b/gtsam/linear/LossFunctions.h index 479c183ee..a8902e11b 100644 --- a/gtsam/linear/LossFunctions.h +++ b/gtsam/linear/LossFunctions.h @@ -544,6 +544,50 @@ class GTSAM_EXPORT AsymmetricCauchy : public Base { #endif }; +// Type alias for the custom loss and weight functions +using CustomLossFunction = std::function; +using CustomWeightFunction = std::function; + +/** Implementation of the "Custom" robust error model. + * + * This model just takes two functions as input, one for the loss and one for the weight. + */ +class GTSAM_EXPORT Custom : public Base { + protected: + std::function weight_, loss_; + std::string name_; + + public: + typedef std::shared_ptr shared_ptr; + + Custom(CustomWeightFunction weight, CustomLossFunction loss, + const ReweightScheme reweight = Block, std::string name = "Custom"); + double weight(double distance) const override; + double loss(double distance) const override; + void print(const std::string &s) const override; + bool equals(const Base &expected, double tol = 1e-8) const override; + static shared_ptr Create(std::function weight, std::function loss, + const ReweightScheme reweight = Block, const std::string &name = "Custom"); + inline std::string& name() { return name_; } + + inline std::function& weightFunction() { return weight_; } + inline std::function& lossFunction() { return loss_; } + + // Default constructor for serialization + inline Custom() = default; + + private: +#ifdef GTSAM_ENABLE_BOOST_SERIALIZATION + /** Serialization function */ + friend class boost::serialization::access; + template + void serialize(ARCHIVE &ar, const unsigned int /*version*/) { + ar &BOOST_SERIALIZATION_BASE_OBJECT_NVP(Base); + ar &BOOST_SERIALIZATION_NVP(name_); + } +#endif +}; + } // namespace mEstimator } // namespace noiseModel } // namespace gtsam diff --git a/gtsam/linear/linear.i b/gtsam/linear/linear.i index 5404964a1..d4a045f09 100644 --- a/gtsam/linear/linear.i +++ b/gtsam/linear/linear.i @@ -203,6 +203,24 @@ virtual class AsymmetricTukey: gtsam::noiseModel::mEstimator::Base { double loss(double error) const; }; +virtual class Custom: gtsam::noiseModel::mEstimator::Base { + Custom(gtsam::noiseModel::mEstimator::CustomWeightFunction weight, + gtsam::noiseModel::mEstimator::CustomLossFunction loss, + gtsam::noiseModel::mEstimator::Base::ReweightScheme reweight, + std::string name); + static gtsam::noiseModel::mEstimator::Custom* Create( + gtsam::noiseModel::mEstimator::CustomWeightFunction weight, + gtsam::noiseModel::mEstimator::CustomLossFunction loss, + gtsam::noiseModel::mEstimator::Base::ReweightScheme reweight, + std::string name); + + // enabling serialization functionality + void serializable() const; + + double weight(double error) const; + double loss(double error) const; +}; + }///\namespace mEstimator diff --git a/gtsam/linear/tests/testNoiseModel.cpp b/gtsam/linear/tests/testNoiseModel.cpp index 7d2c283a6..725680501 100644 --- a/gtsam/linear/tests/testNoiseModel.cpp +++ b/gtsam/linear/tests/testNoiseModel.cpp @@ -512,7 +512,7 @@ TEST(NoiseModel, robustFunctionAsymmetricCauchy) DOUBLES_EQUAL(0.961538461538461, cauchy->weight(error1), 1e-8); DOUBLES_EQUAL(0.2000, cauchy->weight(error2), 1e-8); // Test negative value to ensure we take absolute value of error. - DOUBLES_EQUAL(10.0, cauchy->weight(error3), 1e-8); + DOUBLES_EQUAL(1.0, cauchy->weight(error3), 1e-8); DOUBLES_EQUAL(1.0, cauchy->weight(error4), 1e-8); DOUBLES_EQUAL(0.490258914416017, cauchy->loss(error1), 1e-8); @@ -575,7 +575,7 @@ TEST(NoiseModel, robustFunctionAsymmetricTukey) DOUBLES_EQUAL(0.9216, tukey->weight(error1), 1e-8); DOUBLES_EQUAL(0.0, tukey->weight(error2), 1e-8); // Test negative value to ensure we take absolute value of error. - DOUBLES_EQUAL(10.0, tukey->weight(error3), 1e-8); + DOUBLES_EQUAL(1.0, tukey->weight(error3), 1e-8); DOUBLES_EQUAL(1.0, tukey->weight(error4), 1e-8); DOUBLES_EQUAL(0.480266666666667, tukey->loss(error1), 1e-8); @@ -703,6 +703,35 @@ TEST(NoiseModel, robustNoiseL2WithDeadZone) } } +/* ************************************************************************* */ +TEST(NoiseModel, robustNoiseCustomHuber) { + const double k = 10.0, error1 = 1.0, error2 = 100.0; + Matrix A = (Matrix(2, 2) << 1.0, 10.0, 100.0, 1000.0).finished(); + Vector b = Vector2(error1, error2); + const Robust::shared_ptr robust = + Robust::Create(mEstimator::Custom::Create( + [k](double e) { + const auto abs_e = std::abs(e); + return abs_e <= k ? 1.0 : k / abs_e; + }, + [k](double e) { + const auto abs_e = std::abs(e); + return abs_e <= k ? abs_e * abs_e / 2.0 : k * abs_e - k * k / 2.0; + }, + noiseModel::mEstimator::Custom::Scalar, "Huber"), + Unit::Create(2)); + + robust->WhitenSystem(A, b); + + DOUBLES_EQUAL(error1, b(0), 1e-8); + DOUBLES_EQUAL(sqrt(k * error2), b(1), 1e-8); + + DOUBLES_EQUAL(1.0, A(0, 0), 1e-8); + DOUBLES_EQUAL(10.0, A(0, 1), 1e-8); + DOUBLES_EQUAL(sqrt(k * 100.0), A(1, 0), 1e-8); + DOUBLES_EQUAL(sqrt(k / 100.0) * 1000.0, A(1, 1), 1e-8); +} + TEST(NoiseModel, lossFunctionAtZero) { const double k = 5.0; @@ -730,6 +759,12 @@ TEST(NoiseModel, lossFunctionAtZero) auto lsdz = mEstimator::L2WithDeadZone::Create(k); DOUBLES_EQUAL(lsdz->loss(0), 0, 1e-8); DOUBLES_EQUAL(lsdz->weight(0), 0, 1e-8); + auto assy_cauchy = mEstimator::AsymmetricCauchy::Create(k); + DOUBLES_EQUAL(lsdz->loss(0), 0, 1e-8); + DOUBLES_EQUAL(lsdz->weight(0), 0, 1e-8); + auto assy_tukey = mEstimator::AsymmetricTukey::Create(k); + DOUBLES_EQUAL(lsdz->loss(0), 0, 1e-8); + DOUBLES_EQUAL(lsdz->weight(0), 0, 1e-8); } From 7d04f4bce6f20dbe3cc23bb90f2398c8d2320965 Mon Sep 17 00:00:00 2001 From: Fan Jiang Date: Mon, 21 Aug 2023 10:59:14 -0400 Subject: [PATCH 042/113] Add custom unit test for Python robust function --- python/gtsam/tests/test_Robust.py | 43 +++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 python/gtsam/tests/test_Robust.py diff --git a/python/gtsam/tests/test_Robust.py b/python/gtsam/tests/test_Robust.py new file mode 100644 index 000000000..c2ab9d4a4 --- /dev/null +++ b/python/gtsam/tests/test_Robust.py @@ -0,0 +1,43 @@ +""" +GTSAM Copyright 2010-2019, Georgia Tech Research Corporation, +Atlanta, Georgia 30332-0415 +All Rights Reserved + +See LICENSE for the license information + +Simple unit test for custom robust noise model. +Author: Fan Jiang +""" +import unittest + +import gtsam +import numpy as np +from gtsam.utils.test_case import GtsamTestCase + + +class TestRobust(GtsamTestCase): + + def test_RobustLossAndWeight(self): + k = 10.0 + + def custom_weight(e): + abs_e = abs(e) + return 1.0 if abs_e <= k else k / abs_e + + def custom_loss(e): + abs_e = abs(e) + return abs_e * abs_e / 2.0 if abs_e <= k else k * abs_e - k * k / 2.0 + + custom_robust = gtsam.noiseModel.Robust.Create( + gtsam.noiseModel.mEstimator.Custom(custom_weight, custom_loss, + gtsam.noiseModel.mEstimator.Base.ReweightScheme.Scalar, + "huber"), + gtsam.noiseModel.Isotropic.Sigma(1, 2.0)) + f = gtsam.PriorFactorDouble(0, 1.0, custom_robust) + v = gtsam.Values() + v.insert(0, 0.0) + + self.assertAlmostEquals(f.error(v), 0.125) + +if __name__ == "__main__": + unittest.main() From 227c95ed7603c19636d4cd569fcf4739647afb09 Mon Sep 17 00:00:00 2001 From: Tal Regev Date: Sat, 26 Aug 2023 10:30:25 +0300 Subject: [PATCH 043/113] Add ninja build --- .github/scripts/python.sh | 1 + .github/scripts/unix.sh | 1 + .github/workflows/build-linux.yml | 2 +- .github/workflows/build-python.yml | 2 +- .github/workflows/build-special.yml | 2 +- .github/workflows/build-windows.yml | 10 +++++++++- 6 files changed, 14 insertions(+), 4 deletions(-) diff --git a/.github/scripts/python.sh b/.github/scripts/python.sh index de073759b..c72e9abd6 100644 --- a/.github/scripts/python.sh +++ b/.github/scripts/python.sh @@ -45,6 +45,7 @@ function install_dependencies() function build() { + export CMAKE_GENERATOR=Ninja BUILD_PYBIND="ON" cmake $GITHUB_WORKSPACE \ -B build \ diff --git a/.github/scripts/unix.sh b/.github/scripts/unix.sh index 05d969d40..09fcd788b 100644 --- a/.github/scripts/unix.sh +++ b/.github/scripts/unix.sh @@ -37,6 +37,7 @@ function configure() # GTSAM_BUILD_WITH_MARCH_NATIVE=OFF: to avoid crashes in builder VMs # CMAKE_CXX_FLAGS="-w": Suppress warnings to avoid IO latency in CI logs + export CMAKE_GENERATOR=Ninja cmake $GITHUB_WORKSPACE \ -B build \ -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE:-Debug} \ diff --git a/.github/workflows/build-linux.yml b/.github/workflows/build-linux.yml index e4937ce06..5de09c63f 100644 --- a/.github/workflows/build-linux.yml +++ b/.github/workflows/build-linux.yml @@ -73,7 +73,7 @@ jobs: fi sudo apt-get -y update - sudo apt-get -y install cmake build-essential pkg-config libpython3-dev python3-numpy libicu-dev + sudo apt-get -y install cmake build-essential pkg-config libpython3-dev python3-numpy libicu-dev ninja-build if [ "${{ matrix.compiler }}" = "gcc" ]; then sudo apt-get install -y g++-${{ matrix.version }} g++-${{ matrix.version }}-multilib diff --git a/.github/workflows/build-python.yml b/.github/workflows/build-python.yml index ca4645a77..480c791dc 100644 --- a/.github/workflows/build-python.yml +++ b/.github/workflows/build-python.yml @@ -75,7 +75,7 @@ jobs: fi sudo apt-get -y update - sudo apt-get -y install cmake build-essential pkg-config libpython3-dev python3-numpy libboost-all-dev + sudo apt-get -y install cmake build-essential pkg-config libpython3-dev python3-numpy libboost-all-dev ninja-build if [ "${{ matrix.compiler }}" = "gcc" ]; then sudo apt-get install -y g++-${{ matrix.version }} g++-${{ matrix.version }}-multilib diff --git a/.github/workflows/build-special.yml b/.github/workflows/build-special.yml index 72466ffd6..164646e3e 100644 --- a/.github/workflows/build-special.yml +++ b/.github/workflows/build-special.yml @@ -103,7 +103,7 @@ jobs: sudo add-apt-repository "deb http://apt.llvm.org/jammy/ llvm-toolchain-jammy main" fi - sudo apt-get -y install cmake build-essential pkg-config libpython3-dev python3-numpy libicu-dev + sudo apt-get -y install cmake build-essential pkg-config libpython3-dev python3-numpy libicu-dev ninja-build if [ "${{ matrix.compiler }}" = "gcc" ]; then sudo apt-get install -y g++-${{ matrix.version }} g++-${{ matrix.version }}-multilib diff --git a/.github/workflows/build-windows.yml b/.github/workflows/build-windows.yml index 670d87b3f..a1d232b2a 100644 --- a/.github/workflows/build-windows.yml +++ b/.github/workflows/build-windows.yml @@ -100,9 +100,17 @@ jobs: arch: x${{ matrix.platform }} - name: Configuration + shell: bash run: | + export CMAKE_GENERATOR=Ninja cmake -E remove_directory build - cmake -G Ninja -B build -S . -DGTSAM_BUILD_EXAMPLES_ALWAYS=OFF -DGTSAM_ALLOW_DEPRECATED_SINCE_V43=OFF -DBOOST_ROOT="${env:BOOST_ROOT}" -DBOOST_INCLUDEDIR="${env:BOOST_ROOT}\boost\include" -DBOOST_LIBRARYDIR="${env:BOOST_ROOT}\lib" + cmake -B build \ + -S . \ + -DGTSAM_BUILD_EXAMPLES_ALWAYS=OFF \ + -DGTSAM_ALLOW_DEPRECATED_SINCE_V43=OFF \ + -DBOOST_ROOT="${BOOST_ROOT}" \ + -DBOOST_INCLUDEDIR="${BOOST_ROOT}\boost\include" \ + -DBOOST_LIBRARYDIR="${BOOST_ROOT}\lib" - name: Build shell: bash From db6a559fb9b2466b29e407133ce0db694ad63cc1 Mon Sep 17 00:00:00 2001 From: senselessdev1 Date: Wed, 30 Aug 2023 10:22:36 -0400 Subject: [PATCH 044/113] add another unit test --- gtsam/sfm/DsfTrackGenerator.cpp | 2 +- python/gtsam/tests/test_DsfTrackGenerator.py | 95 +++++++++++++++++++- 2 files changed, 95 insertions(+), 2 deletions(-) diff --git a/gtsam/sfm/DsfTrackGenerator.cpp b/gtsam/sfm/DsfTrackGenerator.cpp index cf989f282..d9f3f1626 100644 --- a/gtsam/sfm/DsfTrackGenerator.cpp +++ b/gtsam/sfm/DsfTrackGenerator.cpp @@ -130,7 +130,7 @@ std::vector tracksFromPairwiseMatches( } // TODO(johnwlambert): return the Transitivity failure percentage here. - return tracks2d; + return tracks2d; // validTracks; } } // namespace gtsfm diff --git a/python/gtsam/tests/test_DsfTrackGenerator.py b/python/gtsam/tests/test_DsfTrackGenerator.py index be6aa0796..f50e74d8f 100644 --- a/python/gtsam/tests/test_DsfTrackGenerator.py +++ b/python/gtsam/tests/test_DsfTrackGenerator.py @@ -4,6 +4,7 @@ Authors: John Lambert """ import unittest +from typing import Dict, List, Tuple import numpy as np from gtsam.gtsfm import Keypoints @@ -16,6 +17,29 @@ from gtsam import IndexPair, Point2, SfmTrack2d class TestDsfTrackGenerator(GtsamTestCase): """Tests for DsfTrackGenerator.""" + def test_generate_tracks_from_pairwise_matches_nontransitive( + self, + ) -> None: + """Tests DSF for non-transitive matches. + + Test will result in no tracks since nontransitive tracks are naively discarded by DSF. + """ + keypoints_list = get_dummy_keypoints_list() + nontransitive_matches_dict = get_nontransitive_matches() # contains one non-transitive track + + # For each image pair (i1,i2), we provide a (K,2) matrix + # of corresponding keypoint indices (k1,k2). + for (i1,i2), corr_idxs in nontransitive_matches_dict.items(): + matches_dict = {} + matches_dict[IndexPair(i1, i2)] = corr_idxs + + tracks = gtsam.gtsfm.tracksFromPairwiseMatches( + matches_dict, + keypoints_list, + verbose=False, + ) + self.assertEqual(len(tracks), 0, "Tracks not filtered correctly") + def test_track_generation(self) -> None: """Ensures that DSF generates three tracks from measurements in 3 images (H=200,W=400).""" @@ -29,7 +53,7 @@ class TestDsfTrackGenerator(GtsamTestCase): keypoints_list.append(kps_i2) # For each image pair (i1,i2), we provide a (K,2) matrix - # of corresponding image indices (k1,k2). + # of corresponding keypoint indices (k1,k2). matches_dict = {} matches_dict[IndexPair(0, 1)] = np.array([[0, 0], [1, 1]]) matches_dict[IndexPair(1, 2)] = np.array([[2, 0], [1, 1]]) @@ -93,5 +117,74 @@ class TestSfmTrack2d(GtsamTestCase): assert track.numberMeasurements() == 1 +def get_dummy_keypoints_list() -> List[Keypoints]: + """ """ + img1_kp_coords = np.array([[1, 1], [2, 2], [3, 3]]) + img1_kp_scale = np.array([6.0, 9.0, 8.5]) + img2_kp_coords = np.array( + [ + [1, 1], + [2, 2], + [3, 3], + [4, 4], + [5, 5], + [6, 6], + [7, 7], + [8, 8], + ] + ) + img3_kp_coords = np.array( + [ + [1, 1], + [2, 2], + [3, 3], + [4, 4], + [5, 5], + [6, 6], + [7, 7], + [8, 8], + [9, 9], + [10, 10], + ] + ) + img4_kp_coords = np.array( + [ + [1, 1], + [2, 2], + [3, 3], + [4, 4], + [5, 5], + ] + ) + keypoints_list = [ + Keypoints(coordinates=img1_kp_coords, scales=img1_kp_scale), + Keypoints(coordinates=img2_kp_coords), + Keypoints(coordinates=img3_kp_coords), + Keypoints(coordinates=img4_kp_coords), + ] + return keypoints_list + + + +def get_nontransitive_matches() -> Dict[Tuple[int, int], np.ndarray]: + """Set up correspondences for each (i1,i2) pair that violates transitivity. + + (i=0, k=0) (i=0, k=1) + | \\ | + | \\ | + (i=1, k=2)--(i=2,k=3)--(i=3, k=4) + + Transitivity is violated due to the match between frames 0 and 3. + """ + nontransitive_matches_dict = { + (0, 1): np.array([[0, 2]]), + (1, 2): np.array([[2, 3]]), + (0, 2): np.array([[0, 3]]), + (0, 3): np.array([[1, 4]]), + (2, 3): np.array([[3, 4]]), + } + return nontransitive_matches_dict + + if __name__ == "__main__": unittest.main() From f205ff37668c54760edd97127fa6d48f6bc503a4 Mon Sep 17 00:00:00 2001 From: senselessdev1 Date: Wed, 30 Aug 2023 10:28:18 -0400 Subject: [PATCH 045/113] fix typo --- python/gtsam/tests/test_DsfTrackGenerator.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/python/gtsam/tests/test_DsfTrackGenerator.py b/python/gtsam/tests/test_DsfTrackGenerator.py index f50e74d8f..5e579bb32 100644 --- a/python/gtsam/tests/test_DsfTrackGenerator.py +++ b/python/gtsam/tests/test_DsfTrackGenerator.py @@ -29,8 +29,8 @@ class TestDsfTrackGenerator(GtsamTestCase): # For each image pair (i1,i2), we provide a (K,2) matrix # of corresponding keypoint indices (k1,k2). + matches_dict = {} for (i1,i2), corr_idxs in nontransitive_matches_dict.items(): - matches_dict = {} matches_dict[IndexPair(i1, i2)] = corr_idxs tracks = gtsam.gtsfm.tracksFromPairwiseMatches( @@ -165,7 +165,6 @@ def get_dummy_keypoints_list() -> List[Keypoints]: return keypoints_list - def get_nontransitive_matches() -> Dict[Tuple[int, int], np.ndarray]: """Set up correspondences for each (i1,i2) pair that violates transitivity. From 2b5e22ccb51fa157d0e4d346e4d5256b79dddb86 Mon Sep 17 00:00:00 2001 From: senselessdev1 Date: Wed, 30 Aug 2023 10:54:30 -0400 Subject: [PATCH 046/113] remove scales arg to Keypoints --- python/gtsam/tests/test_DsfTrackGenerator.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/python/gtsam/tests/test_DsfTrackGenerator.py b/python/gtsam/tests/test_DsfTrackGenerator.py index 5e579bb32..5a7379c04 100644 --- a/python/gtsam/tests/test_DsfTrackGenerator.py +++ b/python/gtsam/tests/test_DsfTrackGenerator.py @@ -119,11 +119,11 @@ class TestSfmTrack2d(GtsamTestCase): def get_dummy_keypoints_list() -> List[Keypoints]: """ """ - img1_kp_coords = np.array([[1, 1], [2, 2], [3, 3]]) + img1_kp_coords = np.array([[1, 1], [2, 2], [3, 3.]]) img1_kp_scale = np.array([6.0, 9.0, 8.5]) img2_kp_coords = np.array( [ - [1, 1], + [1, 1.], [2, 2], [3, 3], [4, 4], @@ -135,7 +135,7 @@ def get_dummy_keypoints_list() -> List[Keypoints]: ) img3_kp_coords = np.array( [ - [1, 1], + [1, 1.], [2, 2], [3, 3], [4, 4], @@ -149,7 +149,7 @@ def get_dummy_keypoints_list() -> List[Keypoints]: ) img4_kp_coords = np.array( [ - [1, 1], + [1, 1.], [2, 2], [3, 3], [4, 4], @@ -157,7 +157,7 @@ def get_dummy_keypoints_list() -> List[Keypoints]: ] ) keypoints_list = [ - Keypoints(coordinates=img1_kp_coords, scales=img1_kp_scale), + Keypoints(coordinates=img1_kp_coords), Keypoints(coordinates=img2_kp_coords), Keypoints(coordinates=img3_kp_coords), Keypoints(coordinates=img4_kp_coords), From 8c321ba226372f11274f438cf56f9b51e6472caf Mon Sep 17 00:00:00 2001 From: senselessdev1 Date: Wed, 30 Aug 2023 11:17:40 -0400 Subject: [PATCH 047/113] add fix --- gtsam/sfm/DsfTrackGenerator.cpp | 2 +- python/gtsam/tests/test_DsfTrackGenerator.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/gtsam/sfm/DsfTrackGenerator.cpp b/gtsam/sfm/DsfTrackGenerator.cpp index d9f3f1626..0efda0629 100644 --- a/gtsam/sfm/DsfTrackGenerator.cpp +++ b/gtsam/sfm/DsfTrackGenerator.cpp @@ -130,7 +130,7 @@ std::vector tracksFromPairwiseMatches( } // TODO(johnwlambert): return the Transitivity failure percentage here. - return tracks2d; // validTracks; + return validTracks; } } // namespace gtsfm diff --git a/python/gtsam/tests/test_DsfTrackGenerator.py b/python/gtsam/tests/test_DsfTrackGenerator.py index 5a7379c04..618f8a8cc 100644 --- a/python/gtsam/tests/test_DsfTrackGenerator.py +++ b/python/gtsam/tests/test_DsfTrackGenerator.py @@ -36,7 +36,7 @@ class TestDsfTrackGenerator(GtsamTestCase): tracks = gtsam.gtsfm.tracksFromPairwiseMatches( matches_dict, keypoints_list, - verbose=False, + verbose=True, ) self.assertEqual(len(tracks), 0, "Tracks not filtered correctly") From 7fbbb3593c5335b1e2727fcb7f8a01b20fdfdb5e Mon Sep 17 00:00:00 2001 From: Varun Agrawal Date: Thu, 31 Aug 2023 13:19:59 -0400 Subject: [PATCH 048/113] updated Hybrid.lyx to clarify the invariant structure of the GaussianMixture invariant --- doc/Hybrid.lyx | 108 +++++++++++++++++++++++++++++++++++++++++++++---- doc/Hybrid.pdf | Bin 231745 -> 170632 bytes 2 files changed, 100 insertions(+), 8 deletions(-) diff --git a/doc/Hybrid.lyx b/doc/Hybrid.lyx index c854a4845..3067d5c26 100644 --- a/doc/Hybrid.lyx +++ b/doc/Hybrid.lyx @@ -86,7 +86,7 @@ Hybrid Inference \end_layout \begin_layout Author -Frank Dellaert & Varun Agrawal +Frank Dellaert \end_layout \begin_layout Date @@ -269,7 +269,82 @@ If we take the log of \end_inset -Equating +The key point here is that +\family roman +\series medium +\shape up +\size normal +\emph off +\bar no +\strikeout off +\xout off +\uuline off +\uwave off +\noun off +\color none + +\begin_inset Formula $K_{gm}$ +\end_inset + + +\family default +\series default +\shape default +\size default +\emph default +\bar default +\strikeout default +\xout default +\uuline default +\uwave default +\noun default +\color inherit + is the log-normalization constant for the complete +\emph on +GaussianMixture +\emph default + across all values of +\begin_inset Formula $m$ +\end_inset + +, and is not dependent on the value of +\begin_inset Formula $m$ +\end_inset + +. + In contrast, +\begin_inset Formula $K_{gcm}$ +\end_inset + + is the log-normalization constant for a specific +\emph on +GaussianConditional +\emph default +mode (thus dependent on +\begin_inset Formula $m$ +\end_inset + +) and can have differing values based on the covariance matrices for each + mode. + Thus to obtain a constant +\begin_inset Formula $K_{gm}$ +\end_inset + + which satisfies the invariant, we need to specify +\begin_inset Formula $E_{gm}(x,y,m)$ +\end_inset + + accordingly. +\end_layout + +\begin_layout Standard +\SpecialChar allowbreak + +\end_layout + +\begin_layout Standard +\noindent +By equating \begin_inset CommandInset ref LatexCommand eqref reference "eq:gm_invariant" @@ -289,7 +364,7 @@ noprefix "false" \end_inset - we see that this can be achieved by defining the error +, we see that this can be achieved by defining the error \begin_inset Formula $E_{gm}(x,y,m)$ \end_inset @@ -541,9 +616,9 @@ noprefix "false" : \begin_inset Formula -\[ -E_{mf}(y,m)=C+E_{gcm}(\bar{x},y)-K_{gcm}. -\] +\begin{equation} +E_{mf}(y,m)=C+E_{gcm}(\bar{x},y)-K_{gcm}\label{eq:mixture_factor} +\end{equation} \end_inset @@ -555,8 +630,25 @@ Unfortunately, we can no longer choose \begin_inset Formula $m$ \end_inset - to make the constant disappear. - There are two possibilities: + to make the constant disappear, since +\begin_inset Formula $C$ +\end_inset + + has to be a constant applicable across all +\begin_inset Formula $m$ +\end_inset + +. +\end_layout + +\begin_layout Standard +\SpecialChar allowbreak + +\end_layout + +\begin_layout Standard +\noindent +There are two possibilities: \end_layout \begin_layout Enumerate diff --git a/doc/Hybrid.pdf b/doc/Hybrid.pdf index 558be2902a5b88157fd876175ec6f9ae80ad8322..456ce49c4bca1986a443255489ac6ad5053fe838 100644 GIT binary patch literal 170632 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 literal 231745 zcmb5VW4I_ko9?-+y=>dIZQHhO+qP}n_Fmk}wrzX%|2_TobkCfg>&%y`yOJk)l3Z0E z>i#7p^1`At475y8B*RDdM;B!e`Ln~LP)zvr_;!YtP~6=3bfOm4&L)n3@74y+Cc-90 zcE%?7bkZiaX3pmLEc8sQ_`JMOPR@=d1~yRcfIZn7afhP`{yT3}r!4x)MoY{Y-7v2M z%Mp19C}O4f-O7%wRTJ}N&Q-<7s+ zcIVnWb+x`P>VArS6@DM`ZcYcC4!3eo>o7`8ZALsYMojH3zV13d_j|u2gmjspsu?ElJpQd*J5T zAJ{z&-CV@sf-tq3qn(_Gow%&9T(Ecg8+W_bTBW*Hl=%h+QtqCf*TS-Y%eHFtA7*?8 zj%B^R{5)_qTlu`#xd-WdMb%xlO`x7TfxgCo*Dsdoz5c@g!b7tcaGOW#djg>9djKdc z+j+%$I{WnoO5&q{8mr{&H6`|Hca(ik91MNba+0=I+*#iTR6OBx3ktZRvoSd8l&!R0cDr z-)f~LK+8N%4PkA;N}-fzGZ0wUhxGRCwss%3+mosaPXBCa=I-Rgh`5-To;u82^dO7Xb%je z*NziZxGRwcG6Jg1?HFX|>^yYJ4yioR=s}%6G1@JVybW-pKdf`$)BG*}KOfov+)M;f_`t`&v*dbgz}E)tw9paS(PN&#-7f zl&{1Wa8P_h#X$HwQ#-|dKm?T*i{jNNvyNFWzDJ*W*bZlm7ipmQ1H8ebwp@+dSCRI# zERn@hCL;JzPZ;KFF`g>cX(UKUu$wxwIr6zCt9hrP`Rd7TqjmI8_)7JRN6AJvB37>G zuB9wArV^y@!pVW|kk(Uq$1m0;Z(GU=Xk@!E|G*;|LXn`Fo6Pu2iMjh)hvK|cQ%uhi z3NVi&2r!ofqOWK{j_QK^U=#~!V4Mg3NTod90gr;afh4c>Xy5Yd$n6ktfgj4d#Q~#; zqhC^U5+)(Sft!6ih%Dj8qebNKqk_3e#6S%Iz7oyFXG21nZs_FGtKVP5}l%#kAYmZIA!ZMy&0m)o~yi& zir1Mx_(T!{_-Gdx7dtnr*(zEn1KLW2z-_a+p&A#w52Z>E>%dhce;7?N z+Zl8VMCXo|e}-0l_$c^!v3WgsNGlZOj`rhIYOX7e*M_|rU@TtcD@ug*lW?E_QhR2vM z57yN8i)_3?ZVn)`U+de8n(eKUO+y4T(5y8D6RRd!ujL#pA4oP7jlZhfScfrc2Dm4r zpH;*VnFTv_lwLRNExS^y7*#8IY}1m+tvCZ;7qmv9f|Wt}aVAl5zS@j#Wk25xB=+eL z(r{;(bABv4ZlJr?F)>ktbQ0T;ng7OqV6pw4vAH4Kng_G$GHKI1BgJExGC1tsg+Ikc zx!g*Coh}+!YudeD!JS80a6y{xP!ZS2%uqEV|43E$n+Jg!%lE%f^plfgeAc(zaVUOb zY%j3*-QxSb&Ss{&!u+CoKHKW6@)JUPY==kbDUOswd1g8~X~O`sx}hGvmBr z+@*k-4ZP7&nsYqS8U#u6(snh`J%2>Jj3e`h-!xv`MUvRH-qYqdce-h*(pt75WU(;& zblf$ZK@XG_UFwUk_O{`-Y1!JNPMTFda|y?;5uNR5R@BO4PDgsF z9c3HMss;NK8PAT)L8M~a(yb2$b0t+DVh#mV&u?rk$AB>#l+O9g`bhEl*5*>U;bkc; zQ%M}1kpX|stREPdQejIHn9FhU{9Wn$-loUAtVNKN1h8)e%Jxj(ts%&A<+47I-)=A{ z6{KlBsPn9H(R+fE?PLlWljyJh7P)O>p(^SY?`{&L!C@JxeeT`v5g*p7WRB=rC2U)2 z=Sn{9_3cF+^Kq4L40}8znR>S^LR|9(!+^;(*C!F5lFCZnPgE#lyMzM?E6zsE`URRx z+F{YhlDQjuAfrtArSWR@XW`vU974hrj)eKdFoaSz#8!oEq!pzk@_Hl9lZ_mcuKYV{ za=mh_(GsAh841P$K=jH;1X`3!N)wRp-0IV*RZ7=0m1k+U0=2NAOojXcT$NkI<-TvJ8$*C~FuXEgU&hw5hgsrjV zv?L)4AI_Li=Y#;P;)|HGZ$MVB9X6)icY0C<#eBH}MgjNV$i%va2faRf^F)^#0yu|m zR#u4Do74@S3Ry47l5a2#mp)}~l199AX*(q62-O!;JEU#89`&CTRypykp@so`5wn!w zN`~%|u!H&bue5Q@k$~@ut~av}VW)ETR$1^01R))HH;5`&>`~y1;9nlHXEV)scW`Ds zI+uiVWUL6=VylVaqRxLBLbyHnpRJysRwiGQ6~F!R4aVju8&K!Z?33z_ES}|aBBtSh zHyOr5WQ|;p??oE=+5664 zxber9dAXcteWEcP@y!u+*tbGCq;nErhPvurSo4qE*2}yD0&X}RLGJzaPR(C6Bd_OI zXu|H;Oxu}$J79D&L*ab6J)Rg{J3Ftkm|M@O57W1Vdx2_W60a{<7K1zk6vY{MUM%KB+) ziS&9Y1RwXu?PE}COz5uTpc8>(tJ?(&28~PjEZmzdSM)8R+wyyHa6ElL>~G7HMOgWa z^#uoVvb{DvwF$&dRnh}~k){gv?H>!2~* znHk>jhx067_Ea{)N~TW?XPgz8Ruvo#Ph*JfhsH@5}F zxV$3DA0bsP5lDDS3~**ZZ2=Me<^d;Ap>0VH*qw^HvrgZy7Gd1lVU>r6=~zo$za~+ZajN`hd%}PL<*bn z;%7ThLH6G?gZSz1_?;tGdwpRRU2hp=fC4$&1wO%t2_8HMVOM(#vH@BCvN2t0%@6-mzV zD$8`a_SAD6m;{fiM5uEESJg%rP311adjUj)o;wC)Ve4+ucGwQP+N2eS-Qv=HGS)La z$)D*n;0bK#iKVfrq8ow^;)z~^{9WKmLH$C%pt+jG9&-uheBWQoY(g2bkSIg#C6G;2!>W_&i>HCVg!knl z#ArGU&oVd^R;^n|iS|JD1Rv(+q;N=#%_do(^!NngACM-#>Nz!~Dn5Vv%-Qi+B5=j_ zoQ>Iv6Z`OCkH!@;xNs* zB^6d--d7TcM6NAa?HMY#h;Df zR!W|YeZ{yGNcU6JW}g=hV<-E=(NvRCkY>XVbstgZmLXlV+hBXS4HnzGo_G^3y3W4# zM*ztjNQVi~ue{$vp;hxv7F%247Y@=Y&67%N`{7Pxtp+S%w9y>L`JaeyG@6yR)eOEV zSJuM1ioPTP{6GASOZN1>k<~;1DQXHB!uX!3ViaSUgGzX;KJQk5bNjyC;=Qr)+GA}I z)dfdMrc>5YCufe?+KiiodN^Xq^E7`Xd`a?>>Cd+3!SR+*)iZR@Q zw)z6NrB3AfXafJVNf0Q=0|wG&;c7-!LD7|;eC?nSSd>Pz2Q&ujCX1Qw^tg_gHUhG# zn{K)O=GA5e5!N|S6Mas73w%5P`C&;oKEGS?-ktso15(~cgjE;vPm+KL1v)#jSIDs) zJIye{zUyV2qS$GA1wP&FI`UIR@Fu+MH0cZnvCT)E;kSUEEBC@~#NklxWi50N+QhC` zgmoJH!}XFeER%fy`J=PTWte?$YMzED7JaU$!(ZjmA7Z9Y_(Y%B>HlZ>V+qth+dSPk zzU9rw2|ULaW+G=me(jThkA(|&$v{^*wEk4Wl`#!D48<_@GbBz)Wj|DMb2+03I$b+h*;^lkrOi~5=o4U(J%seOuXzQ%6QCx6e$P;BcgZP6PlvqXQILY%xodk zdiykYYBnzh@^ekKwHb5 zGBDz^aWLvY{dEcdBhNqW{%tPGJK7m3nKR8!>5uf$nk_n&f-;x=M{oj%WpX1;1Z(fG~jAq4W_-8PGN2lz;hR^WNO#h|X z@&AVXhh|{HXZTm2P=A9s@c%ykyMh^?;XgC{ZOibVBLVNO-9r zX1BTowP|^nyFX0cGxa3GKSk@T{fS3_6#YeZ>R>qgt%fs@? z)_l$U=lb#e?(y#I=!`Ef_d2W7BJ=t2@vXwqc`KC^d!Pg_UHNWq<4QI zYC%u-gs;1uYs*c?i}T0b&GF#uv;61#d#~z=E$;zKE>vvu?6c*wi?i=t`ggZs>kki) zPu>#2(YR89Oa%PIlM@%~69@~3N*e#to19x&ncj75?uD&7Os=f2)QOoHhR#(rkIU1n zbJboN`)65EOIb&5r-GPTK%Nu!m*2R#@I1^`s`&ca(gOV%_gIM_Z35Kc{R7vaR>CA( zTf<@t&aY=q1LCQ6$29p;MRAgSY{U$k_Vb7xHBg>H93?_e1P`Sm;J6*J_h)wAWg)J- z(26=bowTIU%|g*?-}}hJB{MJD73U{V$@>mvPqEAc&EFL0F3y`K`idwjW@2~RT#J96 z5yURmBaW3#V7bWqs+WD8rX8*o^=Mh;4v$lg@iVnp%K8?IoFrFM+;a=KUTm|^-<-d5 ze%K@Hx5X`$lwQ;@^RuN&SeGBd36Pee@wS+kd&uIDnBo#HuF#d0SUaq!H@ywcRNc}d zK9eK5sarg1!=&0UYm>Nj8;~)=*cr#)(+Z$?U&n z3XcJaOimnVXm5536AZUXJNnNHub*K&88S-7+`#Mn?s-l7&x)juTivwHc49$ocP@=Q zSC(@UlCWx>a_G7GG1^LI(AXAj(x7y_%O*b8-Uv}I)Qbc}h5*1D#t~35+Jx?BXY>B- zY!i4$pTWJ41pV~0we$Q;qPTY$vtsSVJY3Fs>^3hhWbtz-4jv$ioFw} zGayfjz=;)FxqjvPkw3w4j+{-F6tMLba+sDpHDkPSt;22P8E@7(u_X1)Rlth)4BpvN zepv#-x?+HB+?CK;BlY>r(ua)0N&I3&F~sTCQ5rlnlY2V&`Nfn!$ZpcK#ujiTdklkB znc4!g$lDvMz0_Cti5R^X1$;U;6mBq#0#c%JW;dsfOs&7e=B{;lYEsG7u!bAoW;EQJ z2kPglAN>*K&y%fL+UE*Cvz-qPZ$esln*Tl-eo@bpA}#E)BZ}le7s|03ZC-$6)e8Fb zVFC2K3dPZR%V~4Hk<{sN?lon|wk8~(nfnf|{Pa53CE6NwblNY!`ql5r{?zsH?0UXcx>I(sOM#b7cdFcZ@1}l#z#A^MKojH_y{1ks_vbsyB1)5V*(Oc#OY(b zO7uM0z}kA-F^H*K{$$r?p}=8{ ze_pmC9Aj4$MT9XwuWOsCF0kleGfKl>#Q8*eS(QFP> z_Ik?j#uMf5YZ2cHya$wY4F;7g0OtCw#*n11Urx_Zjz4-ZjnW|c4=wTFPkmNi%a1v@ zgwd)%tFWTmDr$pGfn-?|z17~v6@u#s*HwcTJvs9#hLW_YYAmt175^`fm%}s^-Xr&J$EBI-T;%B`5B9ym@ zk}miKcwfoT1`HwKX9N*ow46qzI*@XdW%UZQF1|b_a;nvkl8BUktClS!9B&O*9fB<} zCG%u~1S!yz!cTT^KrwetCY}FNn%DD=i8NN+ky6M2ToBonbn4-l>=yQo7JRA|bE!oW zk!m9Kklm1?$52vNKjS+ci-(d&HyVqo+cCN6Dgwcbl7Pfk+*qN>$Z@UlAnryMgsH*& zcxl)*V6(@Cfr8J_A&64ssL=L!I%c)De>k7Gko|PhYm`cfhwI>Z0K^cEC=OT$Ax{jl zAg4km#4!Jj+b)=Ut@Ow=+M0;c^h5BEDlfSrl68F1IdGkI;mC1K+nx!wb^a@oSOM(u zD(;cPe1;+0$nVw60b@E-5tIC2-ii4HsH|w1V@nk3V6&chx|to)5J?K@KkfB&weC@3 z@HhLWtI9TqAP3r=MI+oKzi6OQRsH=Jz0(7jOFi^K$q=L()l6Zkm7PtWPhEtgNbiG- z92!8_b)%M0SskeuEXCn9GSAPitx>!VLllkq#)Y?)eN&CPVID#Jao@iscU(lc61EQ{ z=Uf!%mq@=p$YZyLPDR*)Qtrn9UBi3iKq`P^TsP>mAfVMajfWVvCWH`XgM0+RR0*=i z7kC%f3c?t`rvZADDXZbRR^R;e#2>nTgv*bVKKD!fHK{wkzrUzlkFKPn_1Y{k(6Q*< zu2j3x%q=bY_-{;0vV&F=-JutRwQwW-fc$s-0QB`R?OV*Lkg4_r856!VeA901NIOW% zI9;3)Y*B?(O$)o1KB|Buk!}g(ak&juM#LMmgp*0vq?tk${!ISDF)Ne&wE7*l$1e6@ zmoX$W+L^FV9I_lM%^*lA}g0f*D?&cE~irSJ1cFT~IL9Ta3D?rsMTKDTm2 z)tG8L>(>}7(m4=2e2E5OA5(1R8n<&i-dku7?K@9a5MeJ1C9T$t7XFi8_of`um_W z=utq?X#n(~}-2vR*-%%0#fA8dt%*SWt^ zAEAXoM)6)7mxQLE<(UMz&8hSE3$R5;g1%d>EYDj{s~c7H+wY;d!+!ov2WMw#f*2Pm zJgBi#!NX*@m6!u+mxGbU`r<$1ZgrwsXM467x>NRC=k#b$uy22A4!<{o_U0F7{duIC z)if^9Y_0K^+EQT3(8MtJ?mev0PV{Um2A$`hoCSgck&CdC6Fg7r4MjxXgSkn%y&tEy zaFF%1=K2c3NCc@R3yca!nC!ah$8n<==B*=5YA>-oCT3_G z!USmf?1x|{?7SorNU&Mze%L0(K{D^V!7dJBs1tkRmglj1WP*mEBHDjn>`W;}alnxe z``zclO8e^fa7oUWV1+`Tp1A6wUtEEFl-FW}TeVwDdycS+bs1m=b117FGe-{6r2*M}*MAr1RpR+fmw+zVk}JcDXu2t!8`h$ zmnX^On7(YwuhX*13M(?&onYxaSq!;Wt|*>SLH?QZV%1)Lpmyx5 z@r*ft@+lh@Zq0o0qT&!1!SshA(s3qH4Zja&f@@F7Jr6pdj!6IxBhRDC4m|gU5Fq!M zV^4!)HO)@*LDtDDqMr|Ne%LEPLN{(a8==TxK{navPRJ1jtBGh+M@mlK--=9CpC{E- zU{+p?ti8anJ`ujsc+@qco9$t2W}ZnsqS!l2;eIJatO5(wyCM=k0X1$eIl-BJqVb`4 z;~rItpQGW%kr3Mhb{Z)6M>1A6rNl`99;wd`_K&V~S|`!tBzAt&-(D$lxjO7eQ%NeN zB-e{;y~4|U$ruS}JBsOZ%9jBVN7QL?L^Dd!E-}R{@`5>UxB(Z_&VL?OZKQY##WtQ79pkQiQ!iS)MJl4wUsO#J}cO{#IIs3=9slr-?hzSeXz^vfrYTOo~;cw~90xAxu9?=gU1?l}mVw zFG{CW?JnBAhM3Ym4fA(ZxJ__)K&wz}wtcx*f+n*NF4PDiO}(mI7HNY<25D+uN>UHT zECHc9Gz_#N{+uqm%VKi1!C4u$M4~1wdy==cte`_qE7GYldw!MtjsucnyrwbpA+kVc z)@Yf$EyV~_-We>2m&QobM@+B!y}VfGo`@JC+%zUI2M=0*xxd!2M0)C< zYIHUYZj}k}{Vh-qJC~)WqPWOqyE6B|;*?c;(M~{S?9o-j`Cx3oZKhRN=tz{|KUoSM zoG-LFtALKMP;s&`NSGOB9s^_puDEPY4rn5RK9+XS!7JwJGN`+Yc@-ir4D?_NfYqhe4LPTyC|=62BIesl#jNfoE34PuDsCdR7RRIA`dvw(#vV=Uys=OZrw!;2 zonF8J^z9|Oc!Po?ZpM(Ia)rF1jI;&{VkRcl1QPJU-ZIMs@cuoLflx}bTzW`*CTAA@ zMdv3RUxJ+}OBA4h!TrLU6cgWBHt*1c1~ag6i1;tTno=(vSMB>ZRx*_SVac>;AE}G_ zMJ{Z=lPb*Jq+u2OyJ>Gul&N(&!Moth?YfDwg{dPr$zozzLteTbIMvPY^AQX?LTF0#w>9O4qB{nRP-%GFs?{yYzDl`4jvt@S zh;)q_735mxfHAl&vmDkSlyYdh9MZHp)#RXNMadCw>wldMHS;124V# z!HP=pxd}0_;nif~G1lsOj+mDX9lC+UyqWX(iCO&m$pl!(zmK_Cwr%vL<>y$2qos9}|}m~&k_{i_bwr+EJyCQC*s_bw$yPmqNuaUy6R z4XKFVl-5=j=kicEbSR*NRmBsvb0#M>`?P_GmMm2KS?^$AFDkX+VDZ7V(R7N~GnDx=Y}0h_hHj1q; z?wc(xy3tN63dM8I&g^{P z*M97E-56Sm0g*&F@J0W8OBfP(?7K`oS7$YiGu=IOif{#Pf`vB+NFHSe3Da7%xk5wl z_`KadRn6#r-EOU32bqmKAM?1p(GA|*D7jLQqCZ1*&RCZehNNcwc#Xc z#Le%@Y<}Q(l-2LpZ#@7MvNPg1s-0L?%@s3fnfDu(^=6vg6@{dR!E9S2EO*Bu#lI0) z3Dyh(sZnV)rt1k6{)tr$y!y5+4ZeaKQ_Usnx+6skPIQa`J;WQWDr%)_-dQMQ#%O7U zlOtBv&r1BZUb_@+O3a%`Z?<(Q@zy}aB(7GL#&hz8fz9V;x zuDvlppRv(nGd|Rm{gjSJN{g7#q-PZ=CoUS%Jru62;%a0i9z9QG{QHZyR>}nMXc27Yk>K~_c*zHQM6_jucJIdVV%yrvpBn)?H2+<70A>?u&}45%OeO$G zN*-SO^?O7F#i6wOtT=0Z$voxb-3OoR#>1K4t3wN8JRAynoL}z13pWAIQ3Go}@gcdE zZsPFGTXc1+v93^bV&j&0eyoEKeQ`(O^=iPLk)rp73$IrCO9>mNE>~V~{hk?TIsyhJ z`;EIbsyMVc$beY4M6xMUID&&yXL!RpBnJE|JdgtA!@-OT=-M7NDBEG7iP-X+1?ak6 zpCOROX)u~i4DtGKNj_N$)pMR()CXU`pgNo~IU#5#4$y2?Ae zrZ!c)rEKbcmCZqWuQhu*9Bp~H4yGw$ie=-V*tI1K@~7eaNw=|EwV}bgl_UQVs9Snu zPC3pTWLGCWQ_s2`K_A1(!O{1zhpl@)V@1Z~5^rv1wlIn=q2`k1f5+9aggCj9yX z-d#+WR)z;#?mlUK76OX28y1^q5ntLIQUxq1#8^U5@i41o&Fd!nzLbd~;G{TRFb^pO zTFi#3XVW36o~$Ze&e=q2Akb6_PI82t`36o?#yok8BsoRe0sJ{$%}&I#5OM$+B9Yr! zzNVOwf3{mbDoeQksQ9K>voFe8NwB87Ab4TDFpj1NkKrL%{4_N!;ZAciB6U0)`5;J= zeEkW>=(PMp0!fW`t|hoqM3C*IrKBNrQIogjxHxYseYMoB;*35PW5x*Tm|gDVwD!F9 zHMj<@4E<2<#r63-W3a_I>{{oBtns|vD+@joH@ZCzh1v(nU2spllMXesJK$xo^U4`v zT+0keM}5IPtN-x)q1jp)PGTwnJ8gKpJ%L%v6|HgZF8G~$q;~K8Zg}*l-JKhi%E9Zh zl`V}}+x%!+LcxK`Q4>lbi#0x*<|adPx(RzpuX~ykBA{fTv+OguDh|;pJX`ps{QgJ_ za*V?6nwMX;8^{N_I1y)59Q5Yi^FS^C#c;H?dd83tcB;d#i>Bg?kZ13!nF|Q@ka!Ol z4Pm)~+(RTDpdgLIt*Zi??q|30@IEep>RSgQ`bkC%(1?OsLeMViiC(x|iys;{sJee_ z^nxAaIzE7&kpyMmKo)3_l~ZC!$_jAx8x`Bm`N{4Duh2WMvXZBsE5Ot`fT6JjuDF zHP)0oi7U?rRE>$e%0N~0QJU4}w7*Qw5vv`#%jGa@8#$5_^I$nqbA;j2Q>|}Zm0L`X zv12>IHBA=EE$YiVq932P5|#B1b@11wO!}< z4T;lyW2)`ELrRmG_?H7>%Y9Yl@2_pd4v39}ds>^Ls6IQ^%;x6M2afuub*g8{px2~+ zX%|yA%yTEfXaroW4PMMdb;QQzzIJr?AzhPLQGcn_v7&zN7~U{FV|BPi;3mgrXw&b{ z3uSz%*KcVWij~C#U1dL`zNUI)mw_&;F1 z|4#D$VT=D>!~d@%60EEo9RH2v<>ZXVUXHo^C3#S-`dAwBY||d}yCPcVlZp7ijkS9r zno(7XEyb>I=K>3p?;OpKZiM^U;rK!0)q^_9ff)=rcYH#(zDhrZ-=(&1Y;Hb1);?cX ze>ZU>>KzUS?H*;KzhYJ63lKYv$zm)pF*Kc{@q zFSqU78=!ZF#`Ue3(LG;1Oe%d{yl=@}$ZhA;EV~Ic!D#W=DKXjl9HnJ{Cbr4tp1b9R z&DkupUTOAu)ux$hY4)AnHr#ZX9vDBcGE1?A#JEWvx%z(GGqK501>IPipl8+SxlkD- zvREH2Y$&B%o1}wh$X1X(F3o(^vurhHkzt>yJzd%1@v7jt02_ZY#*?j~AL=Y$IUieC zg%ad+1`4!}l==CbW>2m=e>Lyuz%F1Fy@3%&x+Y;k zA8M+_?`rS%H_C5pF-{*&LiMmI(O9n0{w z89_1Kn`~AcS##$!V{7Y6!tFw|eEytPhDqfz)OsI$^DXB)6`I~-15emAuh%{|Wriob zYYY?mqZ>Ci^C6P^sd1b_vo^(=WX*kfbyR0y=eM}l=TnJIpc~EnJOKOB^1rto|zF z(GSnnL~0U5D!sn~ny)AR#n(2=R#1_L#*MVr+*<0@d^@v2$SS=?uc+HAFHY`#>p_Vo z_f!CUu}+C89GDgM7pwyKJsnzT+fAw|@(atY8e0+Nrvz-%HGgkK23RY-w7XV)IufIj z0o+?92yn7;J0AQKD0t$va6`2E9?m|O)7w`#6ViL>gK;=qTwG4XV@d|MbRf|SJYKJ- z&RmJJ=|%y#cUqZt7Z_QZOu=Dqu$R`1x(;7>UhU27HjXe)vk()3ZAeNLPwNYR0e+Xz z@9VGxiBi)5HIAa5OI5PQUjTqFIR$3|$qic$HZbh3$j>={MA4fY)qT|$>Y<-FO8Vlb z4YZLaoG!7?$r?2hF9E~Z*`LIntCe8B@D&>OPd#kk?}UwGMNfvu_uEQklP|Izz>A#d zTvq|a%uI_q^25UISCFnO!!f=d{kS%60F&K-@-nW}^b4=YHzn$yC8VqQB4}}&TVVHf zN1~AEBGpjDVD8Z0x>k- z_GM4szS29u3A5QG{U(WWnTbrgjNhi{CtJjGKg4Awz4-nFKl+UC)qghu0%yGHVJ~Nh2vg#A zri1)yl#u3nj2u6}^EJyos2~)!5xK61c?1>v<1O@`K1D8mpB$>^>E8!=7O4H7j@308`5_oCrjE zp3LLVfOXXy??&jljyCcmYZq4Yp{3A12v;{3`fRp7iAt*ZLr4@+2+ZnTV(wZE{23Jx zy{qwvB0UARgTw&Z??ne^X%tV71hZ{nyPG^l2q?~BbdJg+eo@2|GszJ*+RZ0hs>{Ak zi)VSzd4QHRHa|=GtiQV#Pw5_l`x{d?m1K=UtMiCor#<|ih=T(}t^&Vq0P24Q7*mYL zqhdzt!;{Y=nugX~8lDQK?k!>=h(8&5l0rhlS%67XLc2gYpco<&$0kq8x;`h%SGdz- zYsn2~Ev$0E1U!xME7&C%Uj}g}#aYiM>uP64)~~Txo6lO@Rf!|`zBlh5AL6m)-Vnc9 zxZW?ksMl50*y=^;Ko-vAz33SjrbWPc)6Mk#U~+f|I};t(mdK|i(Q-;Ft%g}QWW%wb zX?c^Z8nUl9XlSxv*QK1HD9WY}rkcCw$#~Q&O3IoOeV{$xxdw!W^33w)mXXiY9|?F| zi&MiIAIh=1lFZ;Za>Q|RuA5Zu5dJZg08JSrZ@;k5WKgRw6XUhq)%NB4#4cchzo4hR z>%iDn>d#4i)r+rR?z=oova(TX>0gpwm#H`ViN70#0r=q zRO;MtM}ci?fQT{e?L3>kVREewHFl*QN1V1Ei&@lZ5)MI>C-bKNO1F_?F(fwIbHKZaB9vv7_C%O zw9nUP$;HuIW9Zew@Cn_y-u#b~>iCMuNr-0TprKCdiph*9QR@St{fWqxh8=f`{ zII0V>?fqWmINtBMl4HR0HsCdDgwg<-O9u^X?P!-U1f*cVUs5;G;Rjzmm7)2h=M-z%k*xOV<6=QL1j_YHmK}LbV zla|@2FWGdr6fHW~&;i>L=~0*zVbBxc5|y}$Pol?!^ZxoR*G0DutM*-sucM#~Qfykg zUh(SD;mKdT=l;#=2yw-<__yI?tH(X>lx&&xbxe9-=vWjB?a=eJV_!%C5bjP6EsEwq zET6AAPbv7@yP&tXGea}j?f8xRW)>0t#U8`JbP{D@`7mkNJ-16Zo0{)mtWVyRCXT&C z3!N!u`&MGasQS)glh_NpznxT|kU6an#f!CyQSh;ckrvF&JQogZf5Y_Lf$UJ&6taaR zJ*HR6N~@d^r?_LQbQboUhIJ5l$#3!sn(<5h`o^5>b<-X#p;+}|05MIcM)jv|g6CE4 z<%_N%V`v=~Bb42i^PIAnJVXJe_<0SfQU@c9lp_wkdl{sRg!QN9?i+8<4---sIsSFu z$68G(IUx>L`YNDBZ^Q2*AvBlucuT;bG@?pF)Jl{x6MExFnj3!R9U4yKRJefd@+Ogc zBw&W5nF^wJ2~37b_Ef{b-)te`NqiH-AV0k$B}h}xPODO^4Nks8o}^?}wCoB+K)S=72OkaZ;LL6J+uA0ZGl^Nz_=tWcp zdVH?@DbXc}W#$MzJP@?hKsj?c?YePD6nLv1}%RxE8$Y0}UxhdRj-nJAQ<8 zcmW#k`z-9;n~ZIqNxL;Vdnx;_irwQ^d4w9av3S8<>st@AcultL_Ew`%2>KVQ`TR72A-?(;NzhBRr#sBK4z5AnVb$ zCb@F2VVuR9s0SvV{-Nec*5|_Uudu{$kGzUxA4&!Ou3L7fn5^jHRXUCp-VtR*0vw8V z3i|E!0n|zb#bAJG3rg@j1WdrAR<-dv!iUSS3wDEDp_WLUQ)EEo1=H_@v8O)=`irXYPV9`8TLcY6-;tduld( zY(hr8KL(x3H_BShQLv|ZLp<)37!_4rJkHizbZBA5^Xav>LlN!4unlbLvX29vM9b96Wi>IX$dcU^?uUMO7Z{o(*iH|2T$s6qtwI4da?s!2{ z>!Yl8CYA?L$34ARZAG`4;*e5nJieWx+6$X)B14OK95gMeGcp~-TsY&ESt0sx`@(-oZV7})L&?rdV7MAq34)j} z?}%A}=1GS#8x6qq3#QsFMi=p{DUnVP9U-Zkx7A)o=LY3w@@7HRrx1Ap+xX>3^zU}l zlw5y!Ish>HwPKb4Vs0lKbyO$~w^Q2vJGU$CRpDVLNVah%1s#m?#Tj=uKjWpexTG#3 zF`u(yLNRyFW`8Fu;+U02L?k7~a~u&;O?a2Hod1imbAYaF+4g>%j&0i=+qP}nww-ir z+qP}nHaZ=ngHFEmIrrRi@AvL|-}`orvG%T7yQ`(^+iJR3etrk38;FsdGGhS|aDEN~SL4`wY zEDIoq&}pxj8I8b^d9Ds!4p>_BCQ&nt(RflP-S6Zk+YQ&*DmEn`?3orl={%WIUA!0U z5^geHl0rk_T(teieq3m{MUN2Tnpx#Rp4r##>-*wLr5lz@yxAW>psph^H*vNr-o&zm zH8!!u`L#841^a=okQ1IPrJIh$K-}LqlHzcS?(9tqBJbHG{Ef)T#;7`_uR-PDwQ!@H zq}5P#p&gNPBD>ZL=Re^#IAE4GeXq1FE^-ud4!~+lti84?FJl*gc9XII4{Kn}J zV<0i1=R1F1X~`zbwoCj{uK_kU{3#lss;11c%R~2VI&}5g*Tr$>l2Uvli=M5Bop$D#UR8^1i^iruHH#%7;**4FK*5CBv{q?@&4cCk!VxLMJmM1<$$QW zbm@u6HT%Sc_u@0~=qby5wQtrdL7bCL3F4O5ZJ&$tgU#edf*(m=g7&*4D*VE#!`ByF z;G3ks3D6Pj@^~YJ`I6HN=ijV`6ZhJ`*d(-v4eh8)SIT474bB#j;~89$GuLd6ju%8z zJtDGFJCz6ZwMholvlYdF&EjH;zKv5HQUb*i*_5Dvb6G8lv(njx^44=MssmIwlayCcvx*8p<%oh?VYb;}Z=piXPb2A+g^TQD>+1lWh%v|dN=t!U@ z1AKg!0?hcydU|2eO_RD;$V6Oa;x@Wf+-QQ2W3X>Xdn#ZE*5WMn#RW0ZZ&09L$bfCn zaXQZ~y&^HRF~wSm*O8tgt%-;yRy9@%WNrG+i4>d1s>?wG~KcJ8_Bl@yx&) zyt~;+SNHyH-Hev2t$6e0fzgN8xvhabF4 zE^Evb!Bzo_VAzYe14p!_u{mKdOo23Dy4m!u_ds9+E(xU8K_SCxIS^W9>)9A432+=8 zKh%;3Es4!!XY`P+k#hFuZCB@li%46f5DjGqS2WHcWF+#OX))lIyRXn&A&}_fAigp@ zKfY^0{g!WU^NzFgX2S>L^ zb|NLXVuS2O7_{3GJE(Cr5M%9k_$`!lXdP1SW1&WK&l zI$)+>A%+4JcE*e6fW;W~8xKnS{4Kw1`9v7tPTYEcJ|sQKHWN)lvdp_QDe8I&v_dyX zUIc&T$sBU1#sq#Ea}iaNaF?9m29Ussr=Q*6gka;fi0~8yHO?dazG;k;u!8vA&P!p- zKX5M*0Vgd336F#tm-4Ax%J=A)@I>}`?wR-zt)LJ4-^CU-=#b!SCMLi4wW)u9h0 z@Z3Y@q@8eH;y|K3Lpp__dFWld z%|YY5dI}&gk^JSU5v9hA#uG>SyKf~8rS@yk5Xti?zlE5Mg8{??&DlfX#NMribV7}t zN>UA!6+NP?Bs+9jyeeN0q6VFW2K;!u_gbXr2P#D2=*h(*o>rE|3(Y9u;lRTpE=sFS zT!478>VwSSp)D74J?#_Gtzw3$^qO;H?Ij13@>j^Z*y-G|ohM}#R@mZkKtkvX4`(V| z7EG?CxI+{)JQIj{Oq}?RtY^6p$JnRpYk|;AG|G2~V_IMo@S34|_Se>goh;vXyR@v7iaH8t>7E16qlG^Q5E-x>$lN9~nW5D!)nKz>zzr_bAw~TCW(5U(q z4v?ckDKeVkU1~eWis7bw0wS+=Tw>2aAzDycZPQ8|qut243eL8Q35h^5k087BPDgvN ze8teNq_j zgm{Z~PbI93O%B7om&Gh_TsZ}Wphy8nNzr}d)##FF`VWD--^#PUf=>Udz}-JYa{tum zG5kXk_m@cTzYpB~?)leL{|lAg{~~Zl&kFgcChm6_?msH;{>LcZzv}krnIZo>;T;=3 z!#_iae~b10J$Cmmrv2?j$Nx^ch)*kM>-;H4WW=YHG&gem)GGd|AN(VXs({b%ha3=6 z`(LA;zeRz6Iq%OI1ofTtt!z#I)KohDLrnR%Fz~Z1b71&D`d5W@&vx1zT%< zo4;E`%pDw^1kLmv@L3r?bGqsOqeB0An`l+djhxJW8_P<^1j)$w+oJ!!!u;tUrcdGI zU+)ZTpGyjo4xfSLxBLDqzt8>oiIL^E3;r%l>@1%;(7zq@hdKZ5(%*jg+u%PPAfs>n zsaXB5zOm9b{S>Hv8u`1Ez#r;V8U{LMd>Tfk-(h1`HhgwAI&DZ=@z1+tZpd$AYGwR+ zF|_=ShQFn+?4Mtv{XNz13Jv3@QM7{kc4Efnre>eZg6VTuMJHoxmCtl|__S(&Hbb)f zp6z$D*r)dNvt3%>?GKlJ%17BgSJLn6_zW8x<1_xbnEu7m-wl6R`e&X!`rnuShZ^voU|J%KwzP@3##1pUL`ucpNEeS>Dlrz+8R}?11ju+M)po zVD#q)L+66$YL)ccMcu>+-~=IS1$E8X1@Xyr+`ijxS8h3f2YkBxb>!Zz>RO|-rTjW! zk{jn5)Ozm0?(c`qCu5`U7v0&6Dd#5&-#hgT7WFwEB!ep`r-0FvHh;fg3qF+}>?j1l zA>g&0Bk&wL07JBlj$Cb16qVmv{E;mY-_G_XIZuy{s(WWRq8_INiu5CR4QRMC@E^#dKngyuhZ zkz^$q1s4VQ;5DIQE%DW+4bsy237D$OWCW<@&(oQ^b@5|q3WpEC1Va>@j8zpLIrrEM zG(Z4gJsS2n)>r-*R?oBMgXog?3yJC-8crepGVQqiM*`*rA>1Q~x_N-MZsEJ0%?IJ! z4#`fyds-ysDCEWQwJg`Ea7;?L2D`vT*Sp|FQB)6lM&jmJqDLnHgw^ z8nBa=ZX;8u#;=v@7R?3hx0%N`$>SYspw6%d*B#okrAh5PJy|~l){}|pP5|9)U)kXg zC=!|`wuT;)#v#nAO$6j?g;27Aq#K)bBT_UODz>7Z>stw+%w|CtD3xAzfP6?9PgC`JO|njg!cCKVqbS%s&D)zQmY>T{ zq%R^NJGhEqSXkbH&z(?8>L3!@Bo^H3S_ka6tkRXDHwbZ^t%xJ+NA^F&-5H)VWf3wE zMdF~}5MD>(d?URpY{yOY+p$}tUXNYFbp~v|O%?#ZV%|_K1g{+LO87&zZ2C+_=!L+j z8=jgqQ$7zIw=AIa9Td)WQe;46aH&%@M00Py9RW>KGwPqcN&V7sm@V~Ejxr~z1NdPk z{k8iLkGWy*GOI++x2?5&E&LspbaCnGq@}dTgau4=ia)U3uaOtYo29p8*R3@UAYD#w zG{)v}MljIT*n@{tOKg1()_OTJDFm|tg&6dR#xuBu6@-A`s1T@lQ<1~%>zppzw(*Rv zjEW?hi1Ko~d-!A*mwjjz+hs^br{Yoc42rvUGhE&=Fcj_jV=%00!9{-lms~nJTQ7oW{i9{ z(6oGM{Y&-T{0|<_TFA?f$~^YgQR=D$&$x!}>hEGT3#A2{2*~VE$HL@qyXr2O7=zYH zgC)5OL>A$4xNm33SVBgouD2%xJYAPC2c4>R;-;XJZ2QY3q|CNsyWX+U_zp@1fwa%` z8j6Fxd}$+Jj8sTnWLS^cG|E-s(*(Otar|?{h&BO6*3#mmPvB6V$h4pW5nWadbXt+X zLY%N3v&gdciLXm=3oWRMIy=~%j|^|tmhft!Z!bq!Ac5G{sSY{eTM*p1Q$0iiM+) zn_En}4LBvu{P(i&=QfOnI*=voMH~vbEX<}VPHd6l)MHlMos}848y{EglmVP(Yk=hJbNqO}lODGu0U`TBwYxoJqZOhqgU{+pzqX z4eQt@iT<$yQj#%~FFzi`y#tg5P9-WOZeIj=ST?_5KV=&2uCpX@QoKp5&_CEKeI+a= z^|p~G8`d7~v*LH5X7 zu^f%pV3Yp5cg3R+TA5NcVkoc}L{FJOYiJNxj&1m)O07#WCCz(q^I`uc_cpHyKNQ8m z8oUe8wJ%wIobofhdj+4t z7vFwhshabBcghKmo)tw|8lU>k4xyxjo-{4X5C`NRfmzY%lYw4ggoK?MpxvWepbTjy zyN~-FNZ%I#?W@x!zb1P~YP*ygi==y0MIN|-mByxefZa_v&IUkZq)3x)+F|Dk`{j@<(_dpARx~w%|-^Kx^U`h>9-8 zPw9!BO^fmz#x9rPwj+$r^gb*aMA~2ZtXK?6hc`3iGEXUAJ&5F$so+Sm&KHdnGcVu2 z#}$_;+4Yc!*Mxo@H_b;L^)H54ejr-a$ZQ`out;!_(z%WSZ$4X0!Z6l%El-1lY6xbB zHuqq24=iU&pNq^w?ZQ83Ff(;+L2(=_bi_gSM~Mm@g4#k#bsc}$qE^zAR&1v$8z+4u zU&2{Mtb$9B0|))-Y-CYx^A~%A@xV|KO5n zzMJblXXr}X@QSDSi?KNsDA~nO0!}QfGouCVpMwCN7!TU3+XkPe z7Vk*g1{FY}SXsAvt4tHz*Xz6-j_Q$NaVAcy6KeR03NwF{dgWYr<9}#)9#J*JUAl-} z?J^hV<6KzPN79dAxc_AA8~To$_j<1pec6F0Pi#yiVHI{4U5Dy(b`HmiP+JmeOLtVV z^)u(kM%`q*ufra9i?XWRcdJtLq`@d85md3QA1e^z;O;b$z5(b&@sJry=Z z-JtM^gS~5(S2eh)wLGTsUsak8$mh3&D3-MLBX7lm(91aK)nw&Mc&Up{V#g z(lizYSa)hbvosF7UP_wK!Yy1)-tB2dM8+A4uylhMj^|V(i#xY1Ab#8J1Y1J4oNDl0 z@ou7pvUcR-gV}f!IkzfFi<&I8JU>BPpTbuz7RY<7&A!BMW&wU=fnkf6|mPP&Ucons=lr2Zeju*a`nNeINP$q$FI0yN%&^|V^>_zli$x>`j%s= zHbZ2Rj!x)TnjXTa8p4CX zcd*oF=@#YC86YpWMCx1^ya&ljYuHi-A^I+nSeszZG*<2VZs8=YwBgod;ht8?ID5HQ zje~WYxkNW~x7UR1Mnu|_&vcIrbfE)N)P=Wqubqu_it6#zi;HPYed@Fcu(qN1Ue7Jo^o-WRAfY(keOhrijQWQ))G;5_WGW@8#rZZ(o`B}7NLC2 z#9`)qv9RwLUj)kaPg^v1MZq3TZn+Suyj4Vgo>&~T_8V|?G8qlRNi9fVPybqTK?wuP zuxSomgwi&zyi0>rtiRfci$kCe$S8OB3}MMjq=Z();Y!~rNkh0}x9*%ZKv#}}DN*|= z3&(_bN_ISn6W?yDQdjq_&)fQye?`5#jq9~Eb9}|>@cxx7!ypy1a-;MpkDgW zp&igBA4ArvhyR-BWh%f;wpG=S#Dr>yJ3xIa)aN7(abh8fAHN|#F}+V*;36u8-1pVV#rh2R*<2vYYjcm{q;mf|q{)lWzX z?vOQk@NydTlh4;EGu(=f?xVR#<#IdyeRr!rM@Mg=8;V>0Z!?F7S{j$u;?AYx7q<&%GsR)5hZr z<`Z|?y?zNNS&p323y|_wwO?Fc;g$yq%<&2-F_siu$l zy`}G{eb|UyBJsmE%gb{-+MMEU0KG5Md`24(`3N!~Z`vv>c#btLsj+xEC=NARGZSKL zDLm#}e#Ud>nIU(-XTp18-fOM1~37gC;@>zA2O zuC)Ca-adDc(_%rzD3jY`fmtJRC(|oSEaOYMx(GDtUF8cFi%_%Wh>gR^Z#TFbrD2r> zyGB7V-p*HgRj>E@;>gjtX+fZ)5>sOLD~VTDi@3K&AvuHxcHGI$8)#^W@GaBF*-ko?U(KYJ z1jXAm;W%)*p?71a$Quv27-QU;sd)?xT8+v(T zTZs~nr}vJPuxc{N_*^fgW0_9(te{9Y$cj>sC|9EE#;MD;sME2HvW|1;~3;;W*M4;M5uoUk0M0sliw#VCBW?2-h6kOSxfQomwpD}*7K?pPbr!31 zq&0K2(%j4qwBBU9?KDDP93mdMliuM?Q1K*)S!*E|`{Iw~mDk;c18=M#!mYI%qyt9W ztvgUTb0%!BCfXbpluTJ!(@7r@E!CR5<~vdb3}G)`tGI*8?bq+X#~CuqLwaqun=iFq z1`7B30XXuGAeFRb+F7E3&FC_d6y=ZsIi?jCuqY-g$jEFF)AXB0B{|ohdne4)HcF3g z7dTRZu=q}m?b7n!`4h5?!q9Xo%M2AudF2KwBnb78#=fJ7oK8p51%xi~FX)KaN}+H z%r%{4a(YA*`S$*LWzx76VYWvo&|)-$?N4A+)VdaCxIXuBY8`thiR+@}%ASsI^glBy zS9W3)voE1&+bq3MI?3PlJ~DvmR3w|;-+k0Qbix6A8_OzW7hl|U{8l%>lYfb9PzF5q zKdC+`bVeLoDoAPRC?yz5o4MJQ99zgVm=$P&GcN-x)WoMBb(uHkU^qw*wjVm4b_Z@<#o1S>xgl&XWdIq;;<-gYlHF;_Vf&&T#q5b zSoA94x`<)t9VoL4x|zL^r4?~kr|lfxR)`r{*xgCL8mJ}eTWua*ofrlSpxdgbBM;5@ zlf10{T4M#5*+T6Hi3apKuR0vN-ohHPbHn2aqe56g>f-Rm2DLBK+RcS#GEW`Tg62pR)v^O}H_C6& z`_;f!7fUfOtR93O*!aZLD?z-i4l5a$E5j}Jt8|dbl3;zu@CR8rUHG*uq2ir8%SF!6 zz-B(?aslk8n0T|#n_0Yx@En^aEh!CMDIUC4#>c6q=DE|}S2;zdr;M0AE>?z)2 zU5{@?BAWdhoByMd_?MlzP#(a;t9Kw^q5jZ{lgpxXXM+@%5G6 zkanN&gMu`~4mjk}?G52K@cn4IV6Q2$$dPpRY~&R@J+hI>uK>z6}`dN_y85P>G7y^#i1uS)F+FWes7l^CEr4#t*9lqc9S4z_ZoB~w8Cox z#7};bw+)uYneNMBMD9v6TNb_G1=bcYwl1SmSA2oi&#&Y>46nz?^kfFTdISirgMT#^ z@bQ$fmOMUds|TLPzS?dcJa2aJu8tKj^^Gf2v7T1nK}Uo&N20;ome^{}?DT<>knERf z)h$qO!wB>ccIZc8-T6+Jx+zy{p8Qhr^Er~km$J2#L?q#M9_fGR|wzDcGcljIrE;lD6U&; zP%?2n^kwQy0jIINQV-8V;XByWV6gYv`S2I1dOAf>)~2=R@i44C_qqAlVizVX&QX%L zvpV5#@-QqD7TrbzQ6FHem)v;^imgJln-Ne=yy6rige7gpAl?*)bS!bpsTLosv{1gfcvs3rAIFnxM0I^YfTAD#@tT_hfgO7Xi2P2azl+4f0OAG)VU zGN-`29bmbYz!OyYrEj_Gce8Q5>tydxezEZrxnxULw-1U1NS-7iZBZqqmtRi8YHmYN zhlJMBmzDnNw!#<;o`Lvnk+X7G!P09Cb<31z z=IHLr(ai(mv+A~_xJ}0P$by&pL$o3W&dV*!*<{-ft7{`l-TnnqipqWDj~UR-EwO04 z4CCybh1=GV99$R?F-?(oJ&z+|Rtc}-m2)-cifFJOt(M> zOcsF;wS@%0fgOuLwJd3uh{l{~Z1KShC~|Yibxvlf2{zXVcvUPx44kd@rCiZZZE?c#ie)PHkJ_c6X2FEn4FmOkO~ch!jQ04(<9 zTQ=Ws1=c1frz6sIhNc6D=!S8gs>18h?#gzvp|y2YkLDNU!ojHH zy)RbN=dhgCQ&F&1%Dpm6Y!TfLwzJArGLQ0{b*P+ks>R{oN58^D;I0^NW5Ua06|`+6 zZH5u7X|hbc_n5{p;<5iy5*9hq)_wCs^mv1KfJqBl`vHH47gdD2xWch1qKpqmg|U{F zf=wCPHay=qhdmKcs$h!R#du05j!xsD-kzLDO>7*(=Nzj9&Ovi!2OP6hB<3yc7#e-s z*9Ds<31hx-ut_Z_#v`^@<(Ede`yq%~k@pUsH90tM?RDu1pVI6poKfV$f*7&eKKisw z7k2kZ8BvwF%~RAhdj7mU8V6i_01h z73OQ4Z#%DLeF`&OcPv$k%x*QY4U$I}s^$lB6Pq@G^G_Je?=R)m-_d_XI2dI$HRxVE zx${(Mk?$y%$Y=(_{OT?W*JnXkXsk~3SRVq5ZW{{Q?DFcFR!#ewO3#MQf$x)D)JdMy z#lMU~tq`Sdq_S=NFrDM0NNg|Xn}7?LPx|ckHMuV+DT9P@Bl4jo$b`0KokKzZp4Bo4P?w9w&_9S?I49B)m(XOstM-Z_!I8 z3}%d6510$9gfbD%p@13Iro#F2a>>b*CJB4jo&GWI*(jhd8+227mo{*N$}Ang!CmDh z$aFW9Lv6(l2^LRv5yb6Nuo{Agc$Cq5NMI-&eS3t*xE-iy{uhHG0Wh3!ra@~KNCyg zG)+lyv~%=k=xn{zCa8I0=7u}*W@JD!yYY>tDby1rb5lCrPhaCjhIk^_fa(n4C0y4W zk8(NAAS0{&+La^acIGQGHon$aF@LCBd}cdx`UrD5Wdcv^e6b0~P1>w)nE=GC|Ap>L zFTBJ%M%L}Yc)<6;PA2WnQNiQ7niFvmNPTf@`cteLFS3?WgI_-sb3HUPy*bU_AdEK7 zEOqv94{SqEEHG8Z8BjI8`{k~h0z0-i>cB?f2wR*6824SgFjy*S?jc%e@Y{l&nwqUi zM^DDvV~kr;FYE|{ViZt_%Qy}{L?~PELTzt@Os2kfa^X?AzkyUwb!d@dDK3`x8~m)W zlrEsMn9%H{V`fpkF=tfkoV-*yk^|178+lfA|74*VhOAS61r} zV&gZ)!OB4YSNiWisEt3`*w~o=+4xtA1ZF0>zuNvaxxkrn&|MEnST5BcVT4v zP1XE9{@KP#_nU8H{`}^@K}P=>!}=c}qkr@Ge}s%a^SIM6GJj@`XZk#_voihj#kpgRgVFcB*G;u>+J#m;1Hw zMXrUOp{cRS=~L~tvD%@Yt_I+?wYdqhm8pG}rS*LF0jmHA1o#z|&o8YA1|B~@x0Hm0 z7BDw4Ruxzb;D?`#2n&F)sv(vnl3!&J#OySN7${wXE6^MBTLCa#T?=E=o2bn6M;M8L z?FpEZ!*fPzT0$2M->2A6Op9L!1)sD(mlBAsp7HYm(mRgL$^PUoG#DoP`d^~XjQ}Yh z%~s#u4IAj+ZNK;Ekj40}o$9Bh0u(2ceg!bhZ7af|9^iFLsAp^dul<;?IzPY0TiFT9 z{5S^Bef!kH^&*=+qW!QiB(gPv0+EYY-QZ|#2gm|&cr4~4+e-T=pfruP^#?QV z&y<)RDhmr+(}U-$!T02w83>F*65%7JLbG=&KVL9}2F5zprbb5a($=@5`O&fao)F)5 zlql+tqU;aiJ3MB<_xOjI_K%PhMhE-)dw^5A`H!VZ-Cf?shpXi64-p8c4NtcPdi#37 zQ?*nKK&eV!Tdm5;?hny+(rOtQKF$>1>VX{%y^pPrl2|`JxLk;f@%%0b*)U}fNwv0| z)tNKMFX?CEg3Rf{#kP`2TdEx7$$_ZFV6SLwE#+*_m)AcP{fE6aPBax-rUpfB_<@2&sA1cFG6Nc6gE3qRwG5P~cV^f&DR6YLOCgR+2W=T` zox5O`8fd3q8N>7+sKEj!UI?ANB)%*E3hV?-YG4{shy%W_i7Rk3!#dg+_`bYq9nO`C zN&>J^)Az*=^8niTtP4{!LnMv3@v9ebr>VP2hho6O(a&aoFH z%sk8Q^qbXOWHKyrGnnyUS5R7coR`@?WSep`F?EqJZ~fso?cat^px+x3^3nQn3Zk%< z%HS&0zOkE0^Gq|>o2%yt%Fk+=q8dZ^l;$ZV-_$=UX_0>MT2|^y9y=FO5$9=Rg564U z6=cu2^(J0fri$fNesmjc>zn1>B;zZ!<0X}$aaRIfuMncFtATiy<#Trznteb`W=K8% zc!QKSux!5gC3wpwYRD(23wMp*5Z#JlD)8_WhNvs6IC^*D3VI|SeV*n*X=~Ga@%oFx zfH{aPI(tk`=NHxNMA1ot;)`PXLJ#p$xysQL&K|Mz-Zz^E_%n!ZDu)xF4Lr#W;AjoJ z@ZobZ9j!u;%ovfmWiza1`8nFJcOGm%At(rUWv*OToOu)I%?<+da%1T$9%QLlFh)m8 z?2;b>@UR!ps4v2&hj|X!c5Z}6#d_*>?K1mkH0$sONQb0Rxgts`6j@fj!keDoa(CZx6fe>0`K{^0z|T4Wgk*MRcW_7 z6r0TriwND+-u0(gkQlD7)UO^jT}g%HxVDq<+I+yiH!{*YZkN(|7mrg1Uy_f>Vss zNw&U*VYfxgNT$_6xSP$W4JzN}sZ0S=i56GV=3ILSprzdUb2;A`87!Vqr%9y_+>_8)NF0MW)R++rfp$`!-&HN%F}?#HYz(`Q)+xh9#rk zHzJI*`=^f$4-5Iru0r!fRE}+H1XzB!L={iYJAxA3z+`Wr>KLOkIoOZ4qqAMz~Mtart z+kg#M5QU0DxZOs#>G`RP93fr}&ckh>=(zlS7UYE?BNxHIUO+?a&yG4FT=j%eN+_YT zF~=un{DRZ`Z?MssT7|^g%=-rtjkTvvanEzT;#^0S;6Gscs4bIx9_^^G10I zWbJ!5{vA68pJ~Ia)Pq--psIo>?7^fNR^z^ftsay^bnlRNln6nUyEbV|Z61X+n7F#* z8eJt(5r#b{d4#QzqtAC~sUf>nR6R^4^hs3y1i_fIOa(LNEa${}Abmlu&g`($7nl*r zJye{h7OM)89LIF_vjs6I!N&cuoP+Ikj3t+p%WOE3la9nx8{(w`MKFR}oadY_Ea;+B ziHxsiJnqQdGcSUmx8!9XJ72P=q4dJd)34MZc;L$*O&)Rhg*Sx66mRS%V8EdgyD;Fb zSJv?WGSJOT@S&w~_AL%*tP*QkZxq+bBF#rr+(8?`MABGy0VOiX?wMcEgEO$OQ$Q69 zBRCw^V$eB}DzWGQ^keEIGW>*4dfcZ1Tf_WH99dNZ?LihxosVv78A?`sHBnEBXVj3X1SgQJv}gPw)Lh%@lqEp^+F%h2tVhbrlMd4?Ij}E=g2|N)irgqz5Qt@}OL_(u zgu1RDmSiCmqs#QmSer$M*yQ%nQdme3T$ak|rl4mcpFF2ZKvS2s`Q}KJzvSdb0X^EO zgJ^Mk^YD&nS4E{LdmRW0?=7A~e65kWk9n6Iep`($U4FpTnIw7lO$QYF4B|E&c8p!I zP1{x`yLU#89+5U(uo$lVFwyWu=K3MCMGb$tp}pRXZQk)Er!YTUU7#G1As(fUP3*i6 zc7DOQR~xGF~;(q%xnM)^1p=RhngqTXbrM=~Fg-Qj4zcDW{9M)}fx`TF@3)!&S1 z1vmT>9>}GXnAeQC=lB&5F7uHxBCV;lkT#_o@R1aF^OC^=FQM)NGt;X05+4x@FZ($j z^_A}@*3^|{ag*&=Nr^cqYO01tbT`|UpqB*}C=`X>r zN)g2Q8~oddt3E|jjz5&jM0ZJsQA{)A5%Ytur@a_M7yze~rY;A__0nBb_-hdeScJoN zWUL#QQc>ql7&W_d-GptX8euX5B?8e4{6}xN>@;{esF0gAID>|&n0u!b5 zgsxLuZ)teaZhl3Dndq5FaIYX_NR2N6=!*dSkm7`wM|vljf}-FGfA4xYm~ajdhvMiB z8WFktwaOP}VS{>2c>$Mym^wQXTrTRSC5eA^Ina1cKxJk=@VvKa^f*;n2R83d&5mt5 zm(LxWIJcTbwv3+g#xpcH4Mh{ZcibN+qS&5L0kk~6dY~p0+!YwR~T`ERrXVDNemJ~l2%<$NQ&n&wYYs|hWXH;$=95YgWW z&l2~Uz;(XW(XZxZpC?H9eNmR?WdNJZcScPh46|o6mjphhU8TTZ8OqVwC_D}$1rD4T0Nk;sroBKRs4lnhl` z^kwsCgZp|UtlS?MQ|PfJ_AxP>;58IPm~e*%38;xN0fn0NMTQV>&X%UI zmjETpP-_Vv3#g!`2*r!RPLE|FCpVlxcK2ZRwwRrA~nuE)+~(?DIv7; z)$_#G^W6H`s7EF;$Acy%edv#TPy8Rxn^WV>BB+`LwZ!6$_?Bhw$h?ZWf$&Y(U{tC6 z;#c-lP#R0Fyi9>@>Nx=4hVM*ZzYRGE>Rh;5g_Z*gzqz3jK~>^sZkcU$=70V?o;%FB zRhB0r3AmJZ6iw>i5v2!kGnJ7-_Wo*jLah9a;N{*Bm5(-ixl4F~Mt4k1V*t!m&k=x31pF8-M} zadx$F%DWr-MXPUkz}IQk zj+x8vW9nYUXeCWhm5$VE&foP>|0Ge3cIC15OxLV6AJ+ZimTQ38yEeglAU}ni6zG%wTN?EqJv((6gQ(K+@Nyk;dELn^vT>W7wdd zwpEbdmK;GTcoXNOO&bEZP_(troFTlbOop3M99S$PR0h<85t`S{Y(R2dH=US^2o@+o zx0fxQ16JnbD&wp}gHo)_9!umt8zM7mVsKIzzQt}!+;x&s>r@W=qMKE*al@lxqvgc| z75cfJ%`Z`OWE~Dg69)JDc~>Z4oUd%6UXJF&?{jVtE_T#EjUH+Fvd5M&OS!#Or3Gc0 z=fvgNC$3L7&7B<$nLugR6$8x%~C;0@LfFARt9p?%^H zwGNsYc48_UOoNAlBfa!Qvk($Q9^6bWGyCHwLt34#QT%GlE}mu?78XI4Y^#QHY!Dk; zadb&lysVts3Uu|BdD&)x!ogP1=br&Niqzhrr?;`-!cdQ&cNWpEf67X7%j4h2Bk53= z6P3Us_J2LbLxV%x?@vYaF5+k^$!kkr*a@cI^^+jhAo371eZSje(KqcxbTtyOl;HlJ z9_-MIMR=Pe=~G7h^VBhQQ@Tx@znO(hwF4Pc6m6ZUObPP;F!l~WvV3cwZkxMp+qP}n z?%lSH-L`Gpwzb=~ZQIlTZ{pm0zB^~;PE=H6<(sQ2tKQ0}ideCp=jUU!Roh8uDr6Zy z8X#gaNkhG3kX4&w!*7;;Uz9{%X~N2--Y*g50xC>R@jZ>cLW=oOfpaep>4oZnjUUYIc>0PG`g2xB2Si#7wbti?~d<7vO5TALdj#Ji>jLzIKL2y&jIqH@OEx$Q-*^lbo7PLGIXc}vU; z>_hM*%Ev{!0gc+(U2EOl!x%Tqg?zB_H`2MdTYsK$ibsV&n?5(>UufpcC z7uC?2@%oWC=ULp(4nj4g5ZfRS)t^Ae`@e2 zPPTL954#E5o`$4rr#Tm61cqp37q74?SXxatO=g<)tWBVY@D=%i7Ow8@uOZ}Z`60gn z;n^JpNnUD1eJ>x_Z+c)UX4Fdnx`)y<ET+5--XRd|l{8#jC zM3myJLH1s$=wu0uxL@YvWtTH@$IToc~8gj zr`o9kEICe?P-@}zl5CAjU>uMrJk6i5LdB@&VoX_pz8K^X7>_;=^_DYfGJ&nOv4Ku0 z^aS$hXPuNSsMW1>a0zp?wA*&9J@8^wnpj!%dMlate!1%|ZBfq8k`Q0ry?SBh zP3@DGa}rmEF2Oc#K&n0&fYMh|7-_v>eTt8TT%qe|BS&9A~sH6K>b+oAMG!frOIgVz5`qAp=C{*wY zQA$9pn^LHF8L1Wt;1B)u^KA7`DIVC;yR5Oa$(1X8w%JCNIvK>cw-&)#-MJE=KK>H* zkm?ir>KN&arUm|{DT^`@8i(e?OG7+)(2RTHj_uHn{t5ZO>(GjXh_t@#PJRF#G}0j0asIB{bV;r*o8wPJA2x4^c>7ECi+tJ0 zVT0nY2GB!&w=QCU5)Pg7r zwLayO$yyarHn zZYiOs;vN1-uQRXn>(z@tE-~N|+uWxF*Zxc&Ff?Apco2yY&lNpXiC6=2Q(8bVFKeVN7oa#p@D}>c#VrAU5Dyw^OP{LPpdkXH2f`ZyUfs7ed=$1N6N*|X<0F&5MNHsTlXR5eBn zVUuBrd5cHZ5gSHzp!9_qncPH~=G^3uDO1Yo0xHlux_SSVk9l%4opLwjr4HLKT1pW)dBLaS;> zqS}5K&zt#o;s?Cb!LO%F&AK}FGmtuo0I^9!m=H!4DmldbxOV&jB_Xdkwu1=#{@>Go zhyF@Y)fWta2DLxIxcdy4< zhGI+^A!E37s);%q{jhgf;X0{L#UgG+d8PJwQNGf98h2a{iN z5hEgjCtp~=jnAXr+it`Z*I75nYw;W>eyAhLt=uZPT`sQ^k@no>^VGupf15HrU_cPo z3qH8jUCHL%i?^A%IP|~HgskBczWYnwfPNdxo9l>|HcaTH^5o+iflmm6Q_a6hW61+j zYlzSllry#B^ZP>4%3uajlLRc(nmRC7nhGS`oE-KqzWB{n=|&YYQA$)!&RiyuQMt^g+Lyj2K8!@R`4ay34pi_gM zlV5uf(r^751PB>3$}(T7l}*9LCnIkhQTueu_sCiu1jeIpM zlE{cQixIv%rC45=#8J!b8*@6HLltWpKH!8h7Yt61?(1s3l5%j7JNt(-n3Nh8{6gEh zAjjY~EBUM|r=2s}+P1AK1I_d}0y8s57A+rSw}_pbaQ7>VgpE|CR%S1ARLokFOXU>v zPFbZbaXVt*cWbF%^>vG^Nh1T?5rr+|;-+08VwN*8Kn^mH7)B3CJhf^@*s=xi(Zcf3 zm$vR5^Vwm4iLYb8bb-@Q44TqnxgZ{ii+7I7B=mY&&SN7ailFrMs?pBI z)Y>*3zmhtMkpHHrh6Tab39A@GGNnIp$3gB?4c zGkgwk^%Uo8Kf%?Nz$XvFg|9_1)=Zwhus+hM>j+m=lRw2;dN()~0a*m5m!X!cv0@_%{teQmu50lZiUNa>3s1TPiLI!y&vCmHsWGpTw%iydotNEjcgN>0 zxEL3rf`{k;JGPMys4qD%>nW{XU5HJ(t09u@$J2`6rIa|Cm9Z#8}5Sdu>GL%$UJ z=?60~E*%!tVy~q>PA#NvxJc^xI5bUR1>=aa#`dGi>86_8v(hxLNV-#QwQp}&U{|2F zx|RSCo`Pp-_Q4+1e@(bRqBDy2h&MSlKHnQB`u^%-Bz`1D{E6wvWY;;_aL*oEZKE&p zJ!_Provq52S%5SpIFvs5_Qn?)Pvss)7C`H}X@gMozldx;E-@8^h5v%w7@D#7NEo8E zEehIxDRsA=yfFfoV-LaoiKv}gNnwFIf73AzVx@xa@4#ZvjC$J@%|H}ZU+-(85YDop zgcYDsZ)kgO$8S?}+yL5=wordrH~VZ!kDN8}>1QVY@VN8ex;k|wV8 z;A6gCZNh@p9qObrk^XppIfI_ETfzonOSR3?KhE<=Q$zJvB^BIki($kjcqrGc_QN;H zZb6k;=HGfkFPzySWFDi08S?Dnj8_Hc?C!!baXsHc5FBSz%*`t|C!PtsVg;BgkzZnCmcd5>ZRe=6Uyi@Tr*#Y=^xe2IcRx-*_=5@Fh z=%xbg9N4Uv1^XzDWYyvrNzX8V6Nq2opd%;N8rgPn;N1qs{kpbAAuI(s83U~?C@+R$ z?Af%(FUtn3zVJBfi$DXEhmltgo-c|CZK+bOZYv#FeZceti z$IT`TO}3^Y$7xKzr`ax`=jhmRVKGIc5WF2+ir6no8yf&*780%3MUkicDtx7#8i;6(|hWTxlN$TWM}@&(YTU;>dKBF9#hcuBz9W*^hxCN`*oPlb1d%dtB(~xMQte3(Y?1uc8D`ZsW_Z5GZYF(#_Y9;ZN#z+HT`MSXLZg1sbeb^K7~nW?k7qd^g_)|W=J z&=fpcw-tNtX>$0Ol@E{LiZ+i$6!pSLCG*Z%=a**j2a!CMr7nDFCTa)GNW=4 zQb(L_hJOQ5S3r0v1%sj{guuIE&UtAYNizzp&qldihl9Ymvxf11*UT<~JB!5epC2k8 z+tJd*U}XZHpibp0G{+U?{^k%_q}jadNcE{_rE=4$TUe6PgR;8taI7(N5D{@CmqZQ& z@Y8lodBRdGlt(MUSBr6!3dt#d=(_(H@YJlwL+boD{)^ zb-*95&)H;HHd!(ok#G?Bh^&C2P?T}9%4G0hIEY*q&a#E=4E@L{y1iRhk-ae2#k!N( zlIR9RkQS&59_X93;_-Wo-qXW7BG@8D6TVejIc`!A;$E+2;YD&kUFx06v zZO@dn-##yPf;L)d(ZDi6G^nazo}=)Zo^H8Mr$8eQ2qg=$BO=)hO`7b~!+6kRlh**h zzj4pBAGcb~I_N5T%?8;kzxLN|KRWqP>wQAP5?kAXLH2sA^Gw!0N0zot_C-w-mb+sDwb9_&*E7)M>>-@x8E6M!x{jY7~F{Zt@eb+?b1nE59uW{13z!;uSn&rDY* zCQbZQ&pHJ}3)i#URF%t=;*h~68YX<7IT4snz{0Pz7!v&q1?dJ9cRg3-_ayUC*MVAK zW6;=FnB_+d0c0mflq;~uV5wkrCDw7m+f&nx8R9oPolBQ6amU(R!6>=hVj%SnO2m9y zza;p^uACMKlkMleg)ez6H41}PEsLlbljLEe_~QD;e4H@qLg+h{i|q}lmyut z+HD-iXQs2%y5V4xS@Oz4A8RPtAuX@tC3dSc&i{Zp`78;NQ}twZz{A1}MI zl%aYkmT~k8GW(qkICr`j?$AwQ9A-Z$621$isZdt@Vi9tSI6CpNVCoFQDRcQ1uK8{i z`^gS??{Sd%Y9_st;N^qhs^l zkc;e2gx^V~S*X`mBxq3G!cmWeJNZEqR?hZ@yFMw;oqMMm{3&doJHGNXjJ78-Ya5Ay zqgC6cu#2+QH?0gYr8Z1CzVdy4D<&Zm7Ok_$>5uRCJHK(5RkqyG7@hw0ukY?s0ancs zKUzO_@fXLK&-3F5WyePloY_(q8J=)>I@~Qa&j{Z?Egn0@e@NZ<2C;BWsl38xG{H~o z%fFJLgc^prMbPzh+j(qP8oDSy@?8G@?Qkz zD?Gq|REHsng16yvcwAl5T3iDx>)X3(MK{&L#nlZ~l41wIFQihEA~{@Fah|{7O($Oa z)RLc3rk4FvyPX?Wb-Ui%?<92)F@U$j%!}DW5$kl18$1^4E16Y?_Smgib@qb80|FP) zZ+o|TTZPcKz6KjS_7#FVCH7uMI#>=^v87@WGT zUHxM2oT=Lgk;Z}@aXrA%C(Lx=lLC$88*53(SW^?Jlj*eST4X{@PjYUpeE6El^fQ!w zO)8a}4HHh>l?41wqzNznc!Z^nLl{8tw)={eaa!pgLa1OxYYwBc9fu4ACf|D@XqYM6 zf6b&9`P~LA(*N~#Pv)E<-U$kRnHT?!-?^Tyd)c0BYfvRZx+E7;e<}ApG&h);mq3$PbU;8e)%CZ4yMy}G(rk@v@|)zf@}iUH^Ob} zi5@v-527o%fsyKV8FCVO)S*>+spiMNEG=D|?=VsNzU0@!Mrl*KO7-g39te;GfCQnD zXG=*L19~LZTmP0*`i5G$kgZU1M#He;E?3W(DDvnjIt*Z|-PrnvMS);=uVO%eH)v@3 zR;wjm`T2Fi{fR;`U*w^5-c(4zbWfQge4lpV76bid91Iu-dn<6xjA@(CLKbsZXsBGz z7)t5u4#Q3*znaoTyZYrq)b+1w-PWH&fOYyDhZq>sB=*~E| z!Ukw7D$rIMMRH=cTGuY1e-2w52Z?&0)%68#Lz3utrM=v5S81ww|G^M~)yUS&@g&X4 z`K|IR5lz{m+Fwn$xb;{9nP7O!kwQm>uKM9XknYTPrYXg*bea~s3cHIz+lA(_f z&8&#=Gpk$$6YU<*ouTZmre3jRlHeNio_3rHOHM*?g@wk(K=WVhWq_ECKWljlGw+ct z7jlGkGW2(tfIZF+i_?C0nzk3J8{-3d#9w z-Rbd>%)^`s?wa~`vucM+an!eoB%NN`wZ|RtNv$pyiiKD(C`ie2722oJ1QQTD*zpyW zq8@_G+BiC8=Z$inoC8eCJ$^j9X&KIV`e%zk6KM7Poo zi=-Un4{IBkP?Z-DvoXZGk>X~DZl&w7HG5SJ*LWAN5@oe@tt2{1?vKu;h%v$>_-fby z?Ab{Y+CySsrmke;s6a@uHSH&d!CCU=f~w>G$0Zpu%>t;K4WbZw_Q6ucf;XIDZmP~Y zh)72F>}%E(R1Gx;G)32TsK6umdT|{^VTh%xy%s#!WVztsFD;XJb;w6`8h`jS0@@6Z zo~es9`blgC11;#=$c_rrW`_cekER_s9kFGP|j^G+%UyS%h_z z4u)V*Sn(L)F0L5YTOacys(6`)dOmBqM+aFG#E6fU#}p4W_uK8+>YZ>P&vC!(@?{(& z9s|)Pw8M(a@S;-%JF17*y~K7P8GcEtr{+(~Tu$0HK)!v_KS>|5jenQg%Amt1j?)dh z-7&FFcYNa#kYF-k31IuroM+N@WxR5{U))?hkbY#e8wBTa@+v`hTi{xe?A16tmw@J0 zc5Wzi=P_bE!Eu$qHmL8TCiw{OrD<)p!8mNoqovmaEMkfMC9pyKK6oLMbq$l341mR% z#e`HAR!%fEMZx%Mh8}<)7HZ%49@!T=uN#MeJ537KrxMC39>no&d!V>d5Kh-mG%9^+uU%2Np9kWmC54< z&xn};ml@Z65_gsV3*+nM-1W@HsyOv{BwH%sel+-g@J&VL94VP@)_!Jfwl=07$#v-s z$%wlyZ=5538kBB2SU=3BE>%mWM~ozv|@aNm3?{BlsWP450kw zkSJZd850D-vSCiPWWnsWw``v7u+CjwNlARn?+;%?EB4Y>Jxa4Mc8xH~hj3cMqWeq2 z!j@YIDSB7wCt?;wf=x3C`EoJ%icj@LaJoCAW6{d=@qmV0H(IM{Q(P#K`L3K0ai3oq zUTHVdh=Hn@k_Mk(Iu=PlcT=M~R+UV>1l{ikLd6@UJN0RVQ$0amyj!G~u}n{bg)#I! zX&hX_tyATDW0~~sdP}>jp(@0R;SZI8g6hQ)0GSpCgKK+hdz-puU=SVjCNE#4cwvP- zs6i!`pG=kP%okyUnj8eg7E;d%9>lnSi<>CO9}22f^;b+A3uJ^yy7KsD&YB*2!pAEx zZ5x$;+iv5Xh5fKw3h?~=aN<%*yqu2n3_J;2E?E5YgD#jFyzdS6ip*i=75F>9eF{0U z+f}UVx0?(z&c5Hx66~G6?B9m~+MznKJ}ItTp`iupAz55suPc@7l}R&k{u;UxWh|855~&Xq2ZO1aC8I(qtDsKX9M-5A->PeIx4$duMQJYG$LHAA5x6oDC!bG?jmiQhKN3 zdd+`!pitX6N{tlm=C6g^+1|5iJSu{p9-avS_GRh#^_Y0_D83fb2$jk=pJFy0^ZV}Q zE8A{`tE#oYliHF0@ zMYwC`f&rg--aNP2@MyaclOX*3`J22^Bxuw57@aR5$}ph#iPC_LQeCTgA`Xz4HvFCc z9HX#X%NM8=KN2R)*i;diW9$p!q9_yE&wTZy1@swmDa3}ZB{L>)!2VopbNJJ|ttZHL zDbS1Qkws&TKf3bwg-^ToJ0R!w8~AHz=9`E+bp;^mN3x7-?^qxFsaGODZ1x}6$bUey z|1TT)4{Y{Njp4_y&Gau2^1r;}|5X1Cj{Mh?xw-%G#oPV(zW=Y_NZ}tiQc#_NnfZT( zBY#T&W!L_{gCqZuf&XvdNEQ})sGs}KtNovEY#jd+Ol0Nw2TNpU{g0#n17`XEj{D~< ztUrGHe=x{@w*NZ9^dmL@=kfpC{&VD?_w-MNnVA)z`3Jvb{$U3HHJXK;{=Wf`|J8_p z+Wp^q`+oo+|65o8BLMl+t{=<#kG-Gu$9vAf%=*6vAb)bV{|$ir0Vn?rfMjC+k*5Ez zmUYH|2*m#gKyv)!U;lpqNalY!r1)<>`Tr{X>y!Qqfc!DJ|2NC}&pH2XpXsNC|GtmU zz`(}#b1nS;0+8;;@;ZIZq_TFdT>N%it)6zS&{KVnJ6BiLeUMimFz_2RtsNa8DWhG_ zn_Q2@#b=*6>7NyzRW+X8=UNpm*K0B&w5R&l`B$e9hQ@}*V0VBE#6yi59PEDp@R_&> zQIgovjV=ri^}mN!KnKBRfYJaL_@e<-Spl%%|Avc$!n@i%fM{u`w*#2ToPL>un5|)E zXsl}Z0j9$?!`i48Cj$Vov9TernW~x8R>0qHi&)^p_YNU~(6`is0uCl6IvXJu0W>Ox zV**gb@<3QwLCd?a)HN}J{?$tJzsM4B0&oPU->KQfL;6z|(3Q5x$IkKno6a8GlMR05 zkdDEzHUNiTKu||nQ9=O+pI|DV7M8x54fwCrw~o#A-o!H+3?s|!ic1 z!16kM!lzq1(qlCdT3HFeM5ToG2l8+K7;fAlUi;`u1{Xl$W2SB4Q zOCfNo-^6#mjL>9dhU)s7|=TKAhy*c03t3ez9&=cYR~Mji)!-^ z4f)|qV5A-SeMVqmd#fg}(<7+PA*Jd5XCkpm`%BWs#_CE8`m{P=LO6nl660TjNW&K{0GD5UVL z6Dak-Ltz})01(x|p73%JZm>#EQQGbn$8Hp-K{prUsB&)D&eCe}#3UTlz8m~q--_EW z_rNcp-QSSLK|y+xZw-=tO5TBeppm40S!IM1#s^JOM`I4LjGydcE>{v!l}cs!!N``8 z3L2hp)Ni8wa~D}%$XUEFCdpS^jAkKunBHrQjW_+mFZP}ol-zu7(68~~y)*T-+%xuc z+HAhZQ>{49MoBb*eXxdqzZTWqwv)NWAbVuFw9NFRbt-~UZi7N-H&RQ8#`j zikX$FaI2TQIezJs8)V0~dSM&IOC%28N-#tlOn)IX`{|RqsJRkRHFp)w!E9G{w9UxL zMW$Li$T3~sGt!4wg}pt#o|}V=py~Pz#lK{_hr1kGrxZSpCDRb2X7Ko02PC&F!abei zy~mgD%678@o_MrKa6p)n4x*XcnKDMy2EwC{f`v2&*(z@CtNk5CI}0hgzyzCA-FF9pQXgo9NqS#?f`zs>2+%OmkGe29wyroNp^_mQ6vyCUo^YF_WXgv6iu zi1Kx~M5`L{HJ2dhZpqx=1-h{;AI0NCz!3x#bqGI#CgXps6xCBd6 zLsSn3osYX-eG^-9w@)>?^^^-)IfKcX6`&r#P`vxHdaM5t;kE0i-%b}Fp{nfAol*IG zVj#cMy0(Pp2|~kFDoWkX1d&B-XYXH-4{tO|ksBItMaEbs@*XpST;_|{m<$VOQa7~} z$G8fDR449-CPy_ER$TDl@oM-IK#abHAq91jD8(9JPDkD0x;&})$PSj;J&Uw`o~msK zDz~Rk4im(;X|#EOVMOelef}kED#TY9b{Yxd$e$1X>cbTB8y1tkOF%w+KR=R6$pBg9 zU`R4o0f`kBR8ME__M;k-gU!eS%l;x^J=kU#xZ}A0r2}M8oMBSfzI`z&2Xld5u^UPL zvk60DQK~uI_961PSU>rTxP$t2t)l8eY^N_P=?^qUa?Ga7X?jfZZQ^iyy7+p@gm{O< zRx$+T}r?@*6k-j7xzwGt25y?-=KfBGw^yP zA6w2ZV;!<8{_-d4V8>!-@r<|pJE;3RlDsfioXkiMMMp0LxK zN3V6Lgh6T_K@J{E{8z0f5XF_do#-MQkUc8?sW0-@7d|Evx*y*0E#!w((+4l6@;{7v z9@3KPhQ+gK6s1XEQn|)pZFnM>`A-($@=R$~^Ij!hs&I)@#{$k>rO|9{^Ul+*Dr6vU z*+8iLWIQip#cx>{O5xkmDuQqKPyU{9ieZ|^1Na`izY62pRgI{wDam_k$@FYh9tiBD&CZ29^&(V-rJufac(c+Fmf+gesVV9NLBoW#<7B0<}*fd z&mtq)_EgMx@w#o{8zFGCjK_;Q8+wqIgC_O2Pci6)h6xIIioXfnn52^I*m^;%d%3l= zagSii#0hvVn%U)cpW!OH=P*nz6kfK6eIoq9G>;dU<{9Iv+a$ z+G&+3ytioXeGJe7Y(jGz-q2to4miE~%1S3*%UE0s3zm=q*C@Mpqx-rWA;t&H$T60) zG3V8>Zi~j!R`b(eE)-t z1BJ9u-OCXy zMTk6&$EfqS2x=en2C~)Qw)p+dhlW)I_9%sjO)rWworD~+pdRXis%D>Mrh>gd5bRHvTVI^21ZEN zNg0ORkFP+EnBO67d4)+e-aMm;8FZm)NRxNvkRQKZ(nC_7Qh7`m zBn&CgT1b+A;@-^^xHLDa`NE`_ipfGv;T3osyX$j&0cHsV%yIRk=*lc+J&j%h*yZ?2 z&6_Y^PNm5T8W3yJc{xVh9mjg+OhYb^T)rJmM8>^ajGN}XK06ZPSB(ro7YHZ|j6OE% zGg59V3RDon)H{u~gXGoWbO!p&nSXy|_cEs+vGsiGo@VMa=smy1)##>JLr9fADBg5D z@(2ka8dvD2$7QGSw zO1eI=Hc~Q`xnOWy6WX;#r32yuG{QS!B4}a2g{6!;CMB&uXPSRk{*gr3pA54wSg|+cSL>;+@X|I0<}XGBU>2F6cGXY zuW^~l4tnCP?1PESET}Q_z;*YPu;a9N_|An9%r)`m2Lts9<|pYEnucVIC`yL~t$S-G zIo4(aHV1o_es@VjRgMpmaTa_D0%w=m~-;*PeDN)=0E{7TUb%8S)pGcM& z1bb<}f4d0F>!x<2%|a{G{xU+ z7L4#6R_ZvgMG|1uzaPVZd{4HvnUFhi;C$f4_fum945w49^z9*~$+L85haHq2PieV?o@cmkIsXaccDw?KE02`;>#SSnWW$x58nMF?>%Ig)h z{>KYA({C+srky6K6rF2QgRnAz z5SM-j4R4gF;DOL)Fi?-UEn(;$J0KG`V@jN8Bs^?cmSiU|Sx~Bfb`7Cl&&^oVy9A*M zS2qp9yLZK(Tc*2sktrFbK?wR><70HD;=NRQkoSkKUj}i@Ap3S^2KETO3FZ!!vrc0( z^qg#?O{kCo&9K7k>f@s{Z4?BPXdU4+;at_ICd97a4&7HOd6c~EpL3s0(hI8A)W&o+ zu4aTk8OjxVYcZ9Y`cCsUkGyKP0Clz{bXPm>zxedm3WmhkXQyZmn!Ef!gh`nrO5#cHU@Mw`-Hw0T-oM$S(F=e*%_4_1qHh-~x{)g{cU`pe0O`k<7J;{X~m0 zl#I~GV1Bz+bss$24u(gZXs*C3qIV4zG3Rub>{N$-2M&>)kK1uVD|J{|VA+iK5JNk( zjX7EBmA~ua?HYtdor*m41#8{ugHFgLQuuZ}Cn?BKk+u*)5P3Ux>^LJ`p>a`8jrC1` zP0*io>Axjh4PuTo`}HzL=O=EtjK4b9Yvp?lUBmhYOihjo4e(F~?}rT-f(&Du1{fCU zMMK!QuV@jtgNQ)MCIPS{>7ETtskRvk2=kHIk$ng-Z*hvt$YsaIkP* za5%GV_B{>?U+Kjvc#GM!Quh@48XBD68Cy8oyC!SW7__wX5UaLTk}%5;XAM5JxQ~Ga zY35_Kk~}E;671^xz`oL~s5fawD_8o{3V(ih<8g)f3=Pw|ACU*P6nFy;nD2&US!r2E zUPGM;JK;~AR2SB|8lZJfL5#@5m#5+>ikmnOXoYz%`QLMaN`9(a`=j0#aK+`6nN2$Up7H`#GNwxZ6k0dDP zs7Q?m^gds-^Nl5YFns3lj0Th*x`hLlr^Va50fU}GR(7kz(;p`1r1ufiMngs05FYm$sX$(3-*U34tUa6dwbgt}JEQl5e1DF~*fQ30m`3ZU1Ot4MhL2 zg*RzC-O@j8!0*wS7-^`{y}?o<0#gaWxw>x-6YIpHdIfS{TaO(Ho@k@Wa&RL&>6qz? zN0hTFsa0E>*|}3-#w@P7WGkk`RN9cb*EH64;qq^x%-*J|NLNXH{B6K$I+qQcPQuN~ zaI0R3@B$uLaGpwj$y!dLU73Fw7!ip|g!M{lUV9JRWd2AR4PT3 ztY>9NpQl#N#Au<@P!}P60ZwGhmYn4Z@V#mgEhR3ui`ydHe_(_$LE~=#WuT)E1B-u3RH_|0}tCavO1#7OVdEzsi4?Q-m8@Ftz z2RWx7FGW1J;cb6#U4al z;cy!w8Knr^SIs>n=gwsc^bPrR;nFWB-HjV&`hb$?ZX}|WzEeAnsHxQI#=70oAJBMO zXl5Q_s4Ph38h1EkkRA^fATIh_>r|xBIC;WsV&%&TR8W@kV=*i|M~ScZ8p>nbTXt1j z?h?R63yaNP@5tBd%~1OU_3YITRVZbrKkZNrl*N2;@Hp#Wfb_sBsOCv$+HGc4KuRqh zw|^^1h8eXtD>^E?I(Bvx$U&q`a(>7v79qPN=b{u?{#npO6#|Xb9znkWtw(b-u}bKZ zk{_p_511(#Jlau-X;BhKFB1(riG8Xjs5(HGCSqd6`IPSD_AHl~YIx1!80+-jGf{~e zgk3N)k|zhxXZnj$r02J5?Ys?@W|9&=w~n3u`q>^5Q3T?|iy5KZGBxdwCfu4o2ij!a z)yB^UX#p1W)*TJfv?m7VeIkkvgy!>X$#HZ>QkU@w^Qe7ME7yqMp)j65R}xQA_EdjX zmrfGN2rEQ`mMj~g0b)n%tusKdZFF;;~s`72p8WOXbi^8H+{g zPJUAdwkdPa0ng^L72z*~Z8oaG5jHTMa;4EARZB=1or3KaoA@TfQ}r6wYg?8H`>Mn; zGwPHe)2BuG-Awgw!ixebYD!ofs0S@NFD|yPg)Bko29za%*Ig3SGY28XW}4|#EU;=z z!Kz25(1VtY$u$zXR^Kh4NXi_T6A!Vdbfc(TN;{z4*n|LcL#=+uU8=|KMOWXx#1jcF z$-cy9{qc6fLe%Q%ez$?7Q34IfUteF1pCaPEx)Bb(Pno(cuh*^@%lX7W)kP&GdDFiE z6R|(*uU`xK3aTM|&fROmrPcL=UM)&0D-7Fd{hkoX#zZvc?w4DI_QXdoZ$UsrSSz&x zFYve?(0YrnKM0DRMd4Xq!7Y3I_3kbd%PfE&Gxc#`+&9D#Y`Y*gI>g>P5PpF=A#hb{ zTs7>yv$a$e&MCDUM+LGf5l_v|s6YrpacARkb69Y$(@=%<2;^3sq5AB~#;{#{X~OGB zU=85|xt86RKXD+rRD)YM%0HudzQsJ^v?wK-O@K`b6QC3~(HCv=s{4fB16_#(lGk;b zm`e!>?iHX%jeyb%Y+hY2U;>Mkkob2~EI1Iz1``=gzf8$UL%T|DhR{&dV)21fD!i70 zq!_GE_SoR}GCU?m5@F~u>26eOll7HsYLTmE3Ha{AHVUlBl~0~OJHFzyiCW4Bik|$z zb-$ASEh-xkWjHv%Wwtkd;g_Fbq5`#jA^Tuy)i+)J`3T3~wdoQfxcNI!@9E{x-B=%6@(au#PCci3~2JS4&Tc^Z= zoYLM$w2Rs?NjJeJ>h7#4ULp3}xwfPe=b{OGJI}@zSFL^u!|U)q%_7vnT*I0U#Ph5t zWpRnEM@CE*3H)BxBq52De5VyLz&%~Lm(vWp0|}t_EwUxCKDFM%}ml9!{$M& zs|tc&t95&-ehMij=oP!9X=#C=_kgsRru4vsMLDT{fa6Pemf=2rLAY-PCufH+fb8p@ z%tKa@fGy?Svo21Tx7`Vl-&EgvD%TW8S%o5B-|^%c$SsIKc+nQG+j#`prk8r2K~K<+ z2fE3Esk?m8hzRhJwl4s3LV$XhA40*h;*;ujtlbORte(8AX+-F5W*# zXHR0Wnk(%y&S)1jiS7rTLZSg?#>U3(E?R|PCVI3NV~20q6s(FwFuv5%eF1yGwAxBc zg@}e)$WFqsStIKz-D7FQTr}3~UYvAvYOZxAit{Lf;sL9ZS149~09k~^cdT0(JicdI zOCo3c@Br;gTeOsN??+V71>ds)E}H2h=Us-N1iTB=o1-i^elyF=Elh; zcgWsV8{=*ZhLAToDh{r3@lDDP$6`@|FoH_JFR@z|5ClV^3>iFB&$!n5>25#N+7x83 z{HaOrCDZcrMJy18JXpvIbAIz+07R-zU-%#D#n=RDxUPrWUKmE`WO{ww_TtKbcslYg z8`8|j2s)Apbp_If5C=4wS&U+ck)Tb%R3+c_kM2~uBcAPk3g4F14duSe{_&IAU;pA3 zGmtt>(@&8@`CYQHu(;rVn(Z6cAVlrDz0O?r=T1{LpQzoT&jOYADD=8H%ZxHS`$DHA z+rbi~%!lJfi@Q1Hb)^FRFh=2Za-&#kHAnI!gl(pi<4z*N&2xwd zhSKOacUsr=G(GN4IG)?~Gjl|~wA9- z=y+1^+CG5Pja@B=YNBA+9P&c)k`HGKOJ~XQE9(e2Y9A>I$>Z+(_(ml!us2eH^~yyw z-hwjt)lM~Ms!p%5%`|1;=EMJDbIobfi!b9wVhOs zF)t&|``rJ#irm?~!yDQ-!8Gs~-%O?wAVbW10?uRQ1d6U1`SpdJD#LXH&k*vMox-O6 zF!6Uv1p*3V44D2zLp5c#_opLzNlGg{cw* za+;_SD_+m1Ii%Z=ffmwc*k6H&jP7>$=d8&R3Jzr}(}XFB^7!A~@fi*Pn-4}b@-6d2 z&=i(f+&Sie%6>WFoj~Jp&k=0qlDT^D1xngErYg8VOo<-kyH`C6uHicfU0wSksrc_^ zo%fe{!3p@|T9JNvqzD(MgX26snl+g7HBK}t%6%H?9q57Iyf;CjL$5iNDEJfIkrE7T z!S_7b>(rsx*eEum$!CZ!8!KtuE}*xuX$;9cJo-fBdc>T^-i411?!{bblIkuayD|B%Vy1>T^CY@BJ|z4(gA4KinE6q}(5?ASE86*0p8MiO($7L{5k z!@Ns27!|%eod52N^NF*b^BSZcUU_cMuFL4vWcNN4-Ja+QEYQsN{uHGC4f)>NM}fkXB<$(&p<&vlxsO!G?yZ4?I_7I7}m2e`2*~LiFdF&7A6jVxtHmH zx6-H2+?eY|Ejatr-LL|I26hbGV;=?+#^rH5U@)Gud`&D^W6FT#GOr!sv+pG_iL7MS z9IPi$9dw)VVx=w?CCA2`TdBd_n<+8hOB9)GE+Fq@w-~C?;Bzx-3lGJv|*B; z&WAp~dCE(1`h{JVP<0=p@5%Jv&RGQOscBw!QNs2<@ZcCcc2f{6O{clLOk2vqLZ3@O zhxvto-&-7q!kwj@wnI;G~B~hE`GjU zj9mzJ4F7~_()n|a3JA+OmJ{Jglc->`HKH4as3@6+0hxG z^bWl(fKA^R-*?2jId$z(O^GDtE<;VssUz@9Ez@l{9&$dQ2K*!&8nL7nJt|{`_OQrS8hz$LQ$+DXm9}sMY1&@H z|7gL;n$oR=m%QT`zNRb`S^69FdRS6c4Y?lA9|RXy{KGiRFb8BuA*<3Lwp~*$RbVjG z?NT!mIal^Bo)}5hN^46Yu-zdEpJdn&gLOWb(E}@(PU?7AgNYEb~6l^5b z&(1|GT#f0cpl)*AItpY0N~8A(+5nK-A_){vhRXPu(qbSDYCO&ivB-X~B-%I!y`-QQ z@=+1?v>%bP<`_Y9=rwn%SD+N4i{?XFnEYH{S(H|!N!H*R2?vhKbdU-&iIW?7Oz2_w z^VoDr@a;suOWTDk5Js1cK<4Ez0-ip79eexK_LigSj^^`4_^(fdU*^dHPxGsu#Ux1v z+%W?a6&(6&go5wGk8O{yf7n!?%cP2|86@^XyaO2`BQ#^mxv#}lX1zP3dwzcKPPoPM z`t7gZ+q~I8Ca#BHd%nx=?2nGAoSQqcJ@4GACe3Rx`yxqPZ?z5rKVJw{&}l&kzFyvi zR3)NKDL-=VEQBcMeK)NQ%|4~p!S=bQIbO-Pv5yC5s;P#db2oLqJ%L*!2xBI1^3U`v z3aC07adX?U%IM_9M#8e13!$~QeNs5TD?VCo|7bGV9|v*h;^1^EaOF%D9ty0as{jo( zN_i?(%U8AU&5kh4Y^*$`of=-WW>>LBaM$g@Pa?%mB~>>Svi%KFQjic zr#0j|&4~P+TWfzCxPVVC3l_9@t(V>*PB7=e$MN-iik5hhBLWC4w|(ogJ&TNCWv-)hJtosSK9N!w<^!EbR9s`J`iU*?WOL%~m zS;bp3T0xd0j+m|)?W1+|ow@6Prp2Z5<4py(xf)&FTE&f;*b8~?U7k%8IRO=3sb~)(ISx}v5s;0Pk|nxb(9h}PTFCX=16Fu zZ)cUgjajOb$Eoc!Xrx`IXS*f|-PwPY9+!DPaTt3fi{_dabZ(V8Q>x8sP$*zJ2m zgELW84I%0k8jMJhlv&0b?m(;9ZwIn~#)i?urSl*A6YK)@Lf_Ad2OkMxGN`q)BeBc0tsSe7?9fPwzn_%0FgrqZ1(`x|$c0rEjJW6BM;^g+yf7DpeQ{ z$K`{R1eBJ!EwzvG`&ID6+)NS!7dQOpxSbvEY8XkmdO{HJ+c((D z?o?8LC+l*yLQ0iyh~Bas!N5O{7QQbiC?(iG9WdJY;K_9e0q^gPke=mC#F_C0$&@2n|+7aW2#pO53zsA}VG3)>6T90B*zwjtjs~l5Q7X z(Gd8aH6WN{S?yHS)?fWS!;C#Jv97Nd)2ycj;4YMb3FtbZ+<;F8_x~yO;tJ zyOvR5?hs{EX0SdACw)ut*&y7zS`mT!U7LWJ=DH=0k7taVF8tL>!`uz~KzDEr=4FK;|Bug6^ zH90;>ICC%@#_4ts#7z=3*6Gzns!Ia?E})<&&JF%jNgyYUXOZQf7@2Fr{2^2ZPil+_ z7%~E9c1wZCI3#Ac8db&SP}0m9BNZ}ZM?cx)QCp{jFs?~!6Mmc0VpsAh@cv~CyJaD| z%4cKO;;^era>r}Y1%AUfgT~4bj1m!`V%ZWmKE}3hP#L}e3C-LzzHhH4C=dvo7jWWm zdIlUGnqzSuNK_x`ysYmBh7m3;;bq>;-8JS^Y~5iwmh1%+FwMYH9zMw^XC&FU zbtgV3#5Hf|jvnjn2p-eaq2bAF^haa%Gv%x#2l?O+uV-3>@RFna_q(LJnG?I#5~tQs(f)qzt2W+lqXH=0!h3*5NX=2eLmcWBqo zii2H;5Z;KZMH%xNi$~;JB^)IpX&AA-UsqNkFjPOp3ZNvyM&5ZvMudbV2Mr zKJ#&=N_b(6tJlV8ZrwkAcR|UEUk6Ot<8853kqI#L+|}nDD4BAPBFx^G+C-!)`wr!i z+ZN3;b1nmqEI6Xu=>w9W=2oKBZ-*|ZawmyQrt#oUgP)jOQks{7I)R_AG>+4FhuBGV ztsb!P*H7Yf&vNjSx>{qMNbRU7qCJ?mCUw$YZ+}2pcumBB*TzM+ixE*!qrymZ*j`Zg ztI%OaXg+b|TPD}M!9E(=y?t5RW&Jc_p#+f`t>9;4TR&5zMyPA{z8e4l|LNG0+PhfBA76 z)f=>R2G`BGR{hO?JlwLvPf<=X{sPo%SA_i6GHPs*eo4sV*~i#jGn|oOf@gfb`UwWE ztcKRyrNlE^pz(`$52s|u%sIU*1`!Y zx*ZbR7s);pMARtI`fLd4SY9m0Gi&2Um@sX&QDsueB461FEHOoXKs(5+f~tUwV^{Wi z6#2W4+zkxVud$^)>ExqHD}zkJ7AM(tG+CZzBHjRkY^kNSE|>_60JZ-?lxhpo!=B%s zcf5>yAa2cXq_Q(7STP?Mi%p|HE-`p9innSdqm zYi}U91{kRS4nX`vX#Q7y&dR|0FVx{5V)MT-006-J@9t3lkevUG{wDz9U!=r;73qKJ z;2rfG{=q~1r6K@?bOO5ehW|t(`2W6y|LG1z&;BK|{~P`fRX;S-UuZz#|F1jLUxMP_ z0SE?$FP;4_Qv(MRdqVXuhb30%ukC9z`Y#VA7DC1^75?Ay9{>V7D-+Ci{x)R#IvM{e8R@_Hh<}U!WBOnF%nZ!`I0UvYZsMyQ^uOx=#{V8FBRj)C z%D-d$js5-NzvbWU@7VwKTmRGE|1hWjRQvzj@4wglUmWT03H!VKPcva=X8fwNzo+hs9+{qFw*EyDEI*X)0! zMOglIQGCH1hJ;_F%>PD<{0;o;NdKJ{`Mb=&E}DPte=Vf{%`WR}vHqueLWVC0=4)2} zU$ltDtG$iUbS*dhU=}F~r>wXH-r1j_1gUxVdp%F@@zgZ0Is*QMvFa=YWl@p`+{ z<-GZ0iGAq=kAaY01B(pxg%ck+2f3Swl}}Ad+;=@>EU>5usU;wjEdwia61^?Z zJ@8_XNT3lu&TlA;KB=*>P*Jdi7UqW+I#y-}pgByrFVQGDMus{TR+bmvFcLJ0AoBGs9ci@SPS?pHzcS zn(vb6`$@rSK0WVPIF5O10(OBNUy&J zywMG&6rRa$Br^}ZakhBHg)F}~mvKuepb?qRIwl+X5A8wk9%%4`&vouk!2|CWs4Lm> z&$)NH_Hy5N|7IM%^wL^7K*HCyu^$fsGaAP-KA)i`JAK5zhEK;CtzSgu?qSw=E(oSow3$vA6& z+hU8{It?VVg*gd($Z3^&QC+=~H`oc(Ld03E8PzLtNA`|X>`Q@Vd(4n}HO(yo%g#>@ zRLWyDZOAU}H0nL9+MG8ULEq$crK*@UAG`o_1`F=mHwNBTf5dTjmJ#$n=Yh&CI8ZYI zgdC`1R?Z<-I84WVQ>dhRprsa@awI4yYN!~fLJ+oT82q0LWxA!th?m1(is4wfJ;vGc zFcc{!MD{J}Lp8TUO(VUQ8RjL!63P~&&?w>pY249An~uz57Pi|0-=CX3>XLUM8A_+> zBwE1UI^ot5>LGFv`c&8hXDYs7_+we6Ok|wWYW5Goh-56bWtc*)tGo89qZ?*K>ad4+ z6z^qW4sz%gbD8u_V?MWY%B(x&86T8PBiZ2Kl{tE5v5GGI_45NS)>h)Io+F-_Klb2T z*4)dj-9QRQ`nyqsz-(MgFJ<{fIOVi(TKL|2bmGZ$E`(sRUsL@Np)2iRqIkgy7lbw6 z#=ER;a+RGGV_&aohLQ%n_&YvvgD`~ulrJp9_YHfc#2S<0;aGB}4fon%wIO%`U>0YV z%>bv+Qnsq-ZSG*Jq}@=`#_b3!&YRK3igb``u%Mo$!lBUJAwy_OdCkYM^4Y{1WI=Tc zyku4wS{Tsy$Z#&_+pS|X40$t&c==&IOgk}w?FG69Fd28dNY}?>lQ|yxSya%A5h3_3 zX4t$P8~Ot#ZP1Tv2w!faj;smzi68mFukI(?X^Z|JoYSv=F^VEjy zM;aB+&AVD3xOh4mwjo}ytIf=rDf<>jgMAgNLl}Gn3fi64I7J$Ea%tV!bHeRTol-q< z)4!MohJ<}RZz~jdfM21ghvY`MRfv$tbK`{zoR7qCp($3E4J}X#fl-3Kum^~~HKkpl z0tLNCCQ27Cojnv98w5=53$$hi1Wlo^idmH$E6J0aE;q{dt4izwCPQ|GWf0dny!CG_ zoxkAoMHhlPljNPJ0?RiZ3X3^Ss;V;b^rhrv9*tNLxiEx~q25F|oz5d*k(c`tUtJ=* zXh7Qk#P|egiZ7B-@H0s@hIv+N`u#wip2tCHltkpjcIHSoQPPBe6qM_#6{AyunI`fj zhz=`P7+K^1D!BHDg(qYUGW&7HJGiD`w`?2xCYngQ8^$V=Sl z1%1t;9%sJ)#_~pXGVsLkILm^H4tqU3|B2W|p1P3uEu~Us?eL)Bl z!y#22mT~=B?^x$%h@%xXm88{c7$?DJkj|it55dRXkN0avLn7v^Pihdh`OQUsQk>|J zx;x1`@Y2`G)KH8&T$$v+eGQZIJRWj`2NN+_6e^R;OF2>!k9TQTL@Eai`Ox6(Y0_VJ zkuoIHwE-Hv2vD`k-3vl)vmeutSKm?RT4&2c1;FvFLauf&St zU^X0`8!IXIj$;$$8|g@w(UDs`>hf3&q~cEH0L_CtN-J$#^n6#?-~t}lTo&UWG>N<5 z#+05s<$c0a=iG;$wu&5$%^n1M)D4yi9L<%Pn3w+x=mO*5a5*9EOph0g;!8xekoa+WA5J* z!x%MVTvPnu2rkQR8=_w9S>SGv<+mhqJhhO{8ZtUTnlF!#Hq&;TalY5so3lL8H zKPX)Xodk$)xymy!>-n$mAxy&==#MmCja9y1;Bik%{V>Qq!F`QV1fM2i)~a>yP9;#(WTR_tkj=iHfi&Ws7sk1yn2zTy`ro z1q)hHJF9(H!Y`jk$XVe%X64A(F_h&Mkq;+5Tsw$mMTXuArxMzYl^ZFjO&qMWuT+C0 zr$W+x?_+_xS+};-S^KOEGc2SVgej&>%hATnEnuNs*)TTd)P2}Z0A)ZJta?I&>#BC% z%27PFsVnpKF$$luyNm%1Pq#+l8y56e`p3dkgJy1{y}l2qi8@%Jh2QL+Fs6g+NF&6C zP1_07ed$(jjl=6MAE^F~XJz+@RONSldgA`SsRxJIs`$^wP1=(LHlm_z{ll;cf>idg z7NM?SxxyXzKXPNY^4L+aoTK>}bb|sO2PS^*|9Tu#t0ikm6!~W3Eyv{KHP;rkC^ms0 z>Ja{2b@^ja!S(4ivqoh$FEr}&=hgNh-*)A1eU0BMathI5&k)`#IKunSy*qIttub$cUDeAvuT&XT_h?f^e!2~Ek29I2!w)AeB&Y`5ud2b(>fsl9pYBc5?3JD z)uA-_#%5%IKoprvdvuI;1*qj=mW<%g&ecJRmc7{w#LneL9BHxRMd>jdb2XAz&7zc~ zq65=0Y&D@+#WjdCETj2Kz&qDD5_)3@iCoAa*U|xbMWZ~9v62!v3Fm@6a6QF3L>)K? zSM^lSoh!ypX$Tdf0S@83V|x$Mf?G9B2;qZsjZ2S6qM%#d?gtqa9U$t$0&)?olurp4 zV*}J9RiNpPT}08o=Dd$p<+9<26lPsUnEBf&&c>UEF6hqBJ?=993Q6(Ntyg+9hyed< z<<)nHF)hk#KpBl+dphs$07Ajro1hoc?{KSVkcqs`qqDZFHf#F^S63lBv~pq?hU<$S zqudT!IDIFSOx?+${jP?~XO2GrVxzJc#RCFx>g|%f5%^{)KnSIEu5KF%k)GLMb`L;h zPXX&Zw(%_#+E()zdI#1W@92l-yeC;s-j0Jy!B|GBe*2|7C_)*{TumC06$j+ZvPE-Z zE^NJ&2IyxhbL+g#61I^9coFPzMc;Y$u-|O6NBf+P~T3xHwaof zlChx}uKe41IipW9$u~PWVKLmwQ^Ys~L)xN16_J%Oy)&S0?wn!J;vOBCp++?7jVRlG zmYgbbIRI-}0MYg&u7?F5?GnI80<2mP!c1I>Cl> zOR(Vj;GL3(Op<}lh9r^h%?~` zzrBh@22ZVq_ap06X%&6@o-~2Hr9C3Uk$1(c`ytM?199hQ?YSpm_Wj)AuG+Np+^lwQ zA2Ly5OwwjR0=^=X7Ut=NND-ByEBj*$JeSHVtXvzDvXU~nJ#(L~CykR`dk`&o&?2rE zl0pNopYM(7kSN+bT_J_yHmI4yBP*T9(X&KlN@wCCmL{5&oG*;L>m|Kj9BBm$>`q== z?4ZC~Q#f1lfW^>k4617hoYhazr6cfZwdMZBG`q zRkh?48A#lmf;Kw4J!-&K7TMMuBr())m79Dj;MYRPJ|rX0;B&B-F(cR?-is=b%s~C@ zl;VmKNp$$I$rtFKx#9Cj;|{k(B_uz{{P{>#<%>~~ZV@=7(#hPosDWbo5^6F zQ!zQIUE`+6>F{M9q&F`-4Efu+{$$kqawqS8Pu-SHUQz7UZGym&Rz1Xt373Cx%JDvAhdUw80CVcA zZg&FPh}L2Mr+=)$Gz#9Z?qt`n0!|-fdp)zXR45raCOjK=;^*uT zeK=Wqaln=rqIt?jkIR@4vC4|f^p>2TmB1@58v*~1ja1PL42Ga|VPeigrZ zfOQlVV_%lCu(}EJ%9IfK&17)X_G%c%D&B5s9(P2~7NVS}8v@&(_S*UKv+0Y6xt48s z*VkK@x8)wYJL18DwO8bjdFSZ2DA4lZ%Dwqii&ooR07 zvt=hRbzc`_4e2N+{8S5&8ESaBuI$b~n3%OtBEV(= z;%a7i8Yjo;_qKzg2~|C(bZ1YY`dpvz0}-C*mQwz&dpXqXGepaixYRb?KKm)q`~8X$ zcJQ0y1}l;df}dFURz<*%mgAY1w{usZ)`+E6HawAUspCRCg3(BznTWYbYa8`7%)ErP zRHi|3rsF!I%F05Qrsus$;w`Do1{&V-Xy~3f#a5TS<||m9ICgK>N8PRg(ljBvY0>4T ziiRgkqbkUK=TYc`6G>|EN_=vg-{*4U zyn0=RAJu(uBl`yUfW{{{|HdjkFZQSd7qt`hxNNRJWENbNC7a1>rKFdO&_*u>M%m%P z#EY4*`l9CDGX_ODJ0(3?Kib`?O=Rj@a$~TfvTzNpMi=q;0O0Ik)u141|X-vbr4UPqG zam5=4INX0PKe%Rc`pwlGCiE%8Cu=+Y!MMm}7!VgjO`I~)U-9TLT27wF=|Gf^G3p5~ zl4#le_+8R^)7!=Zd)(sc)kaDc!Z%&RDfw+(#jf>t(^Yp$Of2>GAgDZ4Kv37OH`$!z zpF_&a)2$k${Pz}1dtB z`OD>g6G=1lX`E^8GbZEXEuSx;c+Fu+YC0t>ifMH{gHl+Q*F?JE-?5}5fu~-Z0Q(Z} z^?e5ja29!);e1-S&cry+P`2C^K8bD#lZEAEU1~~N()8I}upBY&TiVLSBoOc-2R z3kZ8$IvK@Ctf-^&R%#p(^UC)Vxi}#~U`3taQvIy3? z)6CR}C9=Z+n{LbWrtqVcK|u)dZdMZ@zYyUUO$i(x_YGPB%~_LUg;^iuy8gaA3}x-S zvMqIVIl3UvXkanOw|GVXW49U+C-<^=r=2G@#S>vaRSuV5F@Jm8ihC12N&+Ho zL{aw*M88MjsWOUTN2qrXc5ykyYf&4PbfQNM{ov{M>4M z@@PARX2OL$JGi1hT!0HUXlJYpyATm`?(ONRsR?6qbVOG)<5Yz+ z=A)!ze&oljsUdG-tJBif8thWkdXS2I_7w5*aA%dz7JW5WBSwB-S}-arQMWbw!Tz)4 zB|eX@WPT{$6a`)PkB6Evtfwp3^3RWO|2}dL-m}9+ahQ1d^n>? zpo0V+KB}Yt(UdNl5DTR@D8S08%?;*5{jGt&(J{mTh@B|1_L|0#Vgv(&QY$iqHbO1h zY52&oR0j}CE|Z{3nX5>R8FmFJ1G&nwk$rMMEyTj)D!O?vN_w_}W>CqtX4#wKC9R%x z08B^6wUDi_)C4vfP3zw%@|D-rZV!p*56_zHpYMRIrnhY)`m9~5NrcLK$$AjCUzB2i z?AvpHsYf<=k_H(y(adxI=3f^`Rsz<&P41!K!&Uwp$!9G|K*In#Elt?XKwYk1+sMOa zljGiEAPE_~n~;6JkfCNWO~ud88No{gNov8)t|Km@e>#yoN-KPb4_IaE`#FtkU(&MG z()zYRRwK1tt*sW#T4f5>2M;+iQ#V5eOMlFAliaY*VdRPz1%On(!M7 z%r{nCV6$Z9irWy&(Qv&mQv;!b(5(as0HH<52SG4FGrJU=KAl@U%|hfSS5Rjm2(v8d z_q3DAVr3UUq>>)y3#2|&bHXSJZa6FaV9$Bld-NrwMl)YKt({ZBGg)9YB)K_%VA2aT zVUG%rVLM*@SWbT7fM?A7g0|E{u>6qHi?nPz(7?1{5n~9X6f^~JaL2$rYb=1G(&raw zky5~+kIs_V2TP7tpS#8e?*)SgEQ24}p@)C!N)=ZigCgeRXXKIrGaloz23q#u6U*zB z>L4h~+iAe(M@2Y;x?TxbmpZ4!4E+d}=Xj0*IoZH075$X#^c|**34-g*lK?MnFu$TX z`mirvz>?|1c?ruo7esQ2I)|C+UC_>kV4qjnQ>3g)FFsdj5vvNSN!bNWhV1;=c8I~u z?@&z48@n1EG`xVz)DW0%l(Q<{?b%vbIU1>Ci81T8_C}z{6HExFk#aJYwHb?!+d^}B z$cDXsl=Ex!l>wRRbhV4$+>Y9h9d;j=ra{^UaWCMpsy7eusk%>eb`6=IbrNUGk>%do zx{#?(Jjpv?wK6C9!5c7kfWX;d;9#lu@bf@)>4{@ z7=3|juEmZkAE34-SsZgLxiuDJ-~SW`k2L~pugm(3qdEb<%)e%@gjP4yRI$W3q4GSR zG%z=`AAD+#rgwyr9(i-J$EN3@(fvz|_+#abZdLT{SD6cvE{U0X@7a1MB=4Fmb9xkf zN@u1)=5yB_p|SQo7PpY1FejBkWvlhC3xQx;G*3Qtvzdr*Un$r7iB`4V$5|F#_A#CN z>CQoY70-&ef)eAv1t_MJJvzs;)}O$3adg>TDJtTyI5m$x$Hm;KLuT$X|8KSY~ScqxiC!SjN zy}hQUR!BYY+R#w8qZfxob@qw%%k9Y+K%)=g!=iKcmbACV4{YbWZO>?s{v?{R!w4hN}$x0VbVvyc6nJUa{_pfDBd$z~WiG zorp~G-K-<7sg-Ir$muNCR>xP*7p{PAjs~0el_kphDHGR^+zsEr%l|Fduj##BT;<(!tsc>>UYKeNa$g-G*>x4nhF!1N9ZDb3Y z1_yh#DBAeHugi}P*SFSErJ0PIkX7STjOzSUlPPy7AQvlc(7^LgCpQY)4O?C!wccV( zf@R^bOkm>=&|^lgACH}&c`#nF@ZkYOKXGS`e_lhRNnl{$u<~HJ1)m?2XI~SfD$lpU zyRZ>)eU$m_rw}tMFG^wxPzD=Fvpb9WKv)-8y=-5~rtTv?&SaGwd|)nPI*l3ax)5s~ zU^)&?fXnYR#4yg=IU{nf5R{0!mb}JyMwG%)9 zGHg>FHQ!aKt91!zh4DS*$D7mQJ4?ViXJ4(i1fr04;{=U36MNO7EMO(jq zVO0bx63m)AOEXl}k(?NQsh#ajN$`gAfyds>aw9Nhw^#`}-ix5su*`1QGUve#%Afhj z$Y_QMtaCoo1=^6^2LhQMVa67~;a#_VyPoayQF_55xHh<{k1IsNl3x zlSAFahNtxnQP;%+_LvG~`!nk(oYxjaQl=RSlS3pzghJJb1?ZaGXArydQu z7HM*dHZgXYv01?aw4ZH(NiFehd2o_cPFA*a)!|r_)7kqslYAknrjoYfjdJ)>8$R10 zp(8mA<9KFd{8s`sA4QrI9hd=axy1i^ObZP@hFdGeesHH^z)BUE_!<|^A zu|pIi@%F$quY>ZNbo__;=~yU;J@se%_6W@ip9KC=Vyi{ zpUm{E$=`<8B={$lejh8Cwdw){XjN%!NJis_<)+ty#y@^fpY?BxC;{)*^b;^e@kD{0oez5p)>4PXtMG5% z(Zut3))RzY{hi0MIF)5A+VhjL=3ea*b=LPnt*?44GwBa`s+$Gy^_NRSVCy8e9#sg2F7r;k$)V2oci*5!G}_Ii7)0- zlx;zUR`1NL!!qT8$q6jaAF`#C7+1D?izuGZy*D(c3++18K+CC2r6pvsRSMTAWGz`@ zl!pc?faf68cT8zFX0)f14n%0$WAmmGC)otymmiKl=|Y-ojGuAe=N_nxDwQ-V!>7+0+B#x-lDGfZg=|9ukp=j z-ixrT&dy#;cU(%iSuPAW(|azKyfCePBsSdz#h9I2vkwPxWm`BL^N&i!csCY7s?naKM69c0p>|AslK6OWH*GxNZ_!AVO7(8fZ2b}Gf&7W`SU17I5d|3HoYk>PYy`j9DRrefEj;kR9L)|WYnWZ&Un`lVV0;qNCMoOY;tM95CiQ77;k|-go zRcvk+W)EXaqj^KUPf>OeyswCUg%xP;q6A4<+|1$YqP!^3j+{Wo0s)>OM%K)^-|%mYG7c51O)C5%M`wN{_GKv_;Q%?%CKs+%KK)*vYO71k&u5aW+B@RF5-c6#z@eK;L z3n1%d@wTV_D4L)p4Bp)^@nr=)tb$&tOX$F~L0SB8oy_L=buZvKj@*7V28+p72bZh3 z1Bl#;GipGmhP8|QY{!Y!n)EafuL9DG*8+3Zs~u}k-@(Msl>|wCYN*M&4T`0KeR8nq z3EkvnTPW%!ZB+^x`*sfSGZC4d-O+ZX{_qmdz19@YPsqB-AQ_;h;j6yI7vT(IjZ5^9 z0_B_A*V`y6fWaV(aCX;Ob7N=QMMM*#&5Jst+QL+7dsPv(DGz9rU)>1Dz9eFtR&D4^ zq$T%i;MQrj&}qS&(C9Ln+>GQp+Sn5Rvuz_gz<^yE5nuXtO?n{vmJX{vqplaUu}iK? zyWKZzK9*m3*F+!j2WctVBtNZ?It;m*Anw{G)mth^PS{VeWP+*I7ARuVoZQix>!Ok* zey~rqi6wpNpde5Q=H2{V%wLs$h``*tEiwJ;?+WTpNRc^nqQ<~4@GFV}6=j&n- zC3N5X@4ue&)JYZ-S05HDKI{aGw$MuuTjTfvMfp2rQF-*^I}`b*5zLn@;`}QR4m`wD7in40G)`;UTMT+g$leTx zgw-a@UqJUyJ&J54+O3!UoKzE}8A$zqoTZa*DoDq);0r!k?}`U)Dcafvp?lBV;qS)r zJf&|mZjWNP(z4(7KS()`W9;G2}RCIvlgv;Qyd*@tUr%FKE%f9AK-a zklIg7`0=sU>_~Ev*|WdJFdgs3*IXDRO|>M-hbtvhOXOPS7u1QsJsfieJS%`6cOF*L z?zEM2F8@8fRr*DkW*1VoD)O75Ng-EQemxJ$`I}RrqC3`Hwjbwfzv%+H1hfwK;G!}f z*#dss(T4b?-7pe+FA+L#yLPjCWpXoc=90{Afh7O8Lx$ibRtJJI`vq&a^yYo*;uzb6 zJuH91i;f22Rxz#ih6!7JYk36?l}TJakY9==fMEaipeyp}B$hwQ@^V7tD2}fY0zX2= z?ak6c3d)l4+q=(7*AajV?;T60u0roa1=ihbi>#1SbuXh;R3sns1y|fXS&>oSp5M;% z_Z*0Y4F4%FF(rn#rzQz~)5dMKw2kz#1gHu(IC@6u!LSxLW_SChgYuRob8OVgX} z6kv@5`zNJ0py^&xqC{hR^h0(iDhI@f-(Ld@;8m_SezO9A2qw@6_>!5NPAyJ@X;Z%6 zJQ-gXe}D`@A2AeEAPxTH-j^2KGjFrc%M-rj+SCZ`r%|bp7d=h8y()TS9Hp>^!h z&%97wiBpW%F}DQ)bIOD@L)Z`m7Q(!vRNCK;hykRN?l(d-a4bT$aVMC7ob&VthK!E~ z#nCHP@*9|uc`|inify$iH+`_;#b@4c7GD*HiY`FoSS;mpZ4io6rc)NY)K(~)K-=IR zalV5eqy9;2Ee1Kcf2K*AVA`EQ!sT7`bY&Vjx9NlvOWgeGAH6hAP4Unme82GZjk$Wv zprP6jbF#b+`F9EDANTj)$Bm}StivU#cM~=?Y9n_`1wn9)5E8Q+jk+_S58$W3Rj0U; zY6v_D?KKbu{FdF*L-W3;`*9x!=PkMLmScqNLxPDn&_g}!O6yq#sq*A#-c_!9{H(%& z2cN0^W@hEy1fTQ$0rj`_@oKGw?0uY(64&kKxy%6JT7dsSwCjy*=Z8RJU{}--X5rqR z$Kczx6Bq(z5)#d`$(1b*JVKu}QV3bwk_Zoo_-9~vurce!Z%DiO`emWRBYcws{xh1*SKUn5_po=#W4p;`F}ty8^9@XY znEDEP#~AsF9@{g|t(nK(scxZNF+Uiv9=E5t5B~rPg z=%tno9o7(x{unm-EZb6E)~ME_c4smmU2+H4TsgD5N3JXyj9X z_$xmbK`Xmx6~x~en?9tX6OT}C z3VSNZwT5}euRY2U&wgs1)YUmofoZkhpRJ!g)gAGP_NICXQ>~#wYgQSDx>Rr^mZ)@g zX<;XqA>bEdP2fkSRwxa|NjhVfsf?O8CZWvThTONtoei-ig7vD;;#8 zaqVBNaFrb0g3ISyp@LtloYDZI0{<6f_W&bHw=Rr-YJ^g5wLY*qthaHnfb7S0=)yYM)Rdaw4CGM?*W z?i@DM{s3((;uGQWF?Pnn3D1?DKync*?hW<1T;OFCB%Uk$%ZcNJfVrBE3J4o2h6j#O zP(=!5c%J{dn(+cnv3x!cVbmZ)yCnVY`-Hi!rJPNHctW4;p$Q$F+lVh1Nc@J9r*I49 z(M*FAsoWV_DJV78!TYBX+90-uDqRjd>#RrLP)~xgZtEL6lq=LmRVEgh^~j6(v|h(t zf2-pw=tgc4;Yva%C6pb+^Wfqc%St{|G(Iv0*3l+)D!RsJmthy&!)ly*O?=H}IItsU zPw)V>onnVz)wK`F^*+;d#x_Nbv=cx@Peb1x?NTSRY-KG?(X;2+U3TZV-)l^gbEDdA zrsar6GqG)iO-=r9>-|ltt~HV;vwfK6)AgGc>?}rLcURfuQ>FBKVDkvJj>EN@4DOJ19u^3omN$iT}SKFPAQ<|p!B#w4`v5-26 zMr17VPidFQ%5FR#Tu1g9NFxR&-1<+32&{gDixQ$!Qwq*KSDk9}CWQ%`Bhf?y?wwVW zy`^0h6Q{t08-y9(COlqX3DpmfuJ3DlG_6SfWTWLKG|`I%Z!-?pB^Z~X798(s9%!ja zqJ5+K=wN%sK1txx#l&BWk-O^Rs!!g%xuiH}jR!@ltT5|0$4a(>H6GO=^qM2rFZEx) zw)OcBEa~1-0N=0jL7#UsH7X_To9M_7y=Akx2Bqz4c7Qx_(xBPvKvG|TP_OsSF_H(` z+HweaX61cC%8rW8#Y!tCWABEvXa$faicZmjV%Oq?9w2Y)i z@j4JZ$_blLxDTg?2;XFE;FT3l2cb$u*W|jMcghT3*1$1}x_ut{&z@=t}T#5Rn94(u`$zwP`@npn#SbPG2UY=91<3rNT^infx3J;xK(Q#?YvG=&J z@QM(|>&2UTco7AG9*O_t$P2;_Nj(krH#HZBKm6CM!$;M)xYlvT1HDC}goDu4)gH7=&J0jnBn(cvBU_2q3O}U=Msv6HZOU=! z5@m{i8MceB>KZBMvb&aH{+1n7ECV3WmX?o+$G%Vu*7`e5S2+Tviz=@n>EuL*@KE|3 zsfta(TzWB`tDxM4adCEF>f}PPcYn3hpHL_GD2WW04G|ZT5oMrf`N;m@%EO?24|)l~ zmh|U!XEg+*5ijDtNJcyixbg3f0n z3wA>)-H+YU4cgEPd*y7w`5hwR?`Mw9bjT~<=#zQN9sz22;b98cw$@v-M^}NY3FJ#t zQOAek1)D#CNC-`DC@Z`No--M$R|wT(EaqwgP!?XkQNWHUdu;F`R1nuQ0sljmNK1Z4 zCIU6ifq{z*OBb*>LuUGmp=q9FQJEGmYloHP;<2u=$UIE@5q!ahwmKKl^-Fn)liLAn zHw5_QdtC}Wt^)ih8JZ=NHs%z;7*hDRo^ywLi{Y1pAu9+(0(YcfiATw%$H@c1E)G>? zrwnazmFHT&3R3MBm7hqHGH9|euF&}PE>=|P@2GOmV8IRd(iqLp%)=0pm@I8d7 zuWkXiI7M3GL9Q%ABz4^xi%0FS8@TdG6zGCV`a}<}$b13+ey9A$;5Ta&WAL`inAS$X z!t_tFjq392hoPH5H3qrP4!*zj58Nuget#(_;I%KD8KXKozbQ$cAaA+f&xs1s-Jxc} zV-{2BZcj!QcPTF?%Wfd3)g(w-t+z#A8@hqbM)WFSeiw~0&_K-z8)5C;DHTPb6k;Zn ziBGFpUR$O3N4@RknoLh`ud8*@_>lBu@aVd~1tBuPIA#lh2OkWn&I$isrT4-?h_0CjlOm?kzX+0ty&91mQ*OzxZsHAD%S&)JGD$*o=x&C46*6Zto{ zRsQ;&&plW^C5Pg}6Dlpu4aT0MO&*I`EDG=j3dUW#laeS}y~qqy0gEc!~ z2YW}n=E38T|5A5^t)%vI-RTCCqr~BTwDZYutScEO<_$`^JPvF^VnkDhb89OZiqqvn z(TH4(zSa^2Gcy^KnGNp?k&u=|R%v^t%<3pT2j8njmb8gmVK^UDFwI6&ygmia=kUrc zPs3$4cwE}%m?WeAwEl*c=1>$|5+}4sE8@?8(L;1|lj;`0kjpqa8uaFb&3i~n!e=e; z3g&TSo5c8ZK#BS;bB1*dqQ!gp!Ls@ek`ROgBZP}FA1J?5q~;+SU9y1o*f+*MY`nY= zvcyHo?8W~8v3L~M0J1aolk;*t_d@tab|#iLt8A2dAp4wYjL0fxsCk!x;MOLW!^p3g z?v>boxeG$ODl|E#4i9JCtFcxksa=1&n%IWLR|$emP=k76#{r}_VfOPqSZEu@L_Q<0 zGhuhd%p!KQ0Hp-8Cp@jW4}^x@fm@tVNfu%M0%q7fWaI`7OFo-Y)v$5Q(TKHiR0qhj zd>8Rd5@o1LsC^pc!PJ$9Cx|V7T&%RSY>TGgE?%3ah# z`#N4@&B^Y}+n*o-`&3aF1ijiaf*2N6ekI8<7*uu9b>v`|LDL5-&buU!9JuT=# z+~L}0KaUoAe0)LZu@JQNOL-#xur0guJbj#v1bm#0dcD)b(eW zQyj5tvX?yh1p9FDq!lXrOK!&b=I9h6Q?_07B81~_(qe8H53y@}eg-Inh0IESuUdQ< zP~`^=^fKgY5tt*G&wn2hLH1QogCpnAF<)m!(4Glamy6e>-8=?fZWe<;Wa1v{Y1`Y= zWYr}BVj+u6O>V6ro`5N2E>jXgt7`pC%;`vMwIo>T_~+pK#Va-}5m$~S94eyTEJ3rT zcI$U015uH77n4 zW@2$kwdxB1a7{@Zl|arRq_i>l!iKCgb=QV?eeBK#nuHea(_xBnsaPV3B@d&%AYJ*B zNqWMRWerVjNylRq4>ghnA2=oVf!=hY30Raj$Gx(%W9zMbYs8ez)0Shp|D0$w>YvKA z>;;y~x22qg&ZtHbVyf(z-JWZoUZfj>pQ@DL*J6}UAisD~%orhSb_|kP6^WPqLzO8Z zy%bDR39b4~ITp#{MrgH9gN4*JcM)PcX6L{4qQ->$Ys zV4yz|2>qI+-OuCwBtT|amb7Nme(+Iu*<|Xw83WQ(Ph1B?kSl2B!Mmqid1SB~7(bXX z->lYRpTsF@^B2WR+pA8eh>|N3CAEi;`bt$W{)Eb$%G(7yvUI@@i1c?~BBIDtU#M;T z+XCiO6=z@uk5WwRVmWlzUvdhg;}+~xqP#6v4C@5CvtGWUk47quU^zdDI9i6K!oL=s zu`vpKK|6afsf9dAdnq=DsrS2|bh@&L0t0B-_gQq7*&-aHTAlw^5!u4DB^3>sjKo$g^+6IhB8=21~Ma*xIW z2I(r2`GTKNR?T!HtlGID9&+$d1a-r!bcGQolW@wy3u>YD-xeggE(B0Ce^AR2A;ej*9fHk!vQcWO6*o=2KG=|lSCg#)K`G^Qs)_))L7D;S{KVy}TjjdST zJ_}j9m6d~3do6YVpOzy`hp|Td!ml-tqF|aQO0_tgVH=yKX%n0vQ^e7s&yf|~pL(f; zjFT3Na5?Q*L-fryzi%i9@JISA!~h+E2Kt>bTU9MB$0B8bFA1*tYC=GfICIx1v`%1z zejY*Zb|jhd&}FGJX^eozwnJcq$`s6Md&Il!-T^e@n#iI#E2#>}tpgl4_7tEll<#eC z>D67)(n!n?0*gmm2anKLOoDdR4ne!0q~Ot4r)`mlXh1wbzrWSOqcI2Yr-gUif<_l z12k6hi9YQU_oS+Lf@1@j56Oa(!gd}rHcVMnhSMc!tq^+PDJ2p#Hg`G`7Ug_CO1n@~ z5OTqE(>+4%>-@oMT@mz@o^it(3*6r~sR<4-bV%Fha5mnzG?jZT2`xFyESzUEZ zbxau24l^?uUw1Hy28=ZpbTQ=fY4(Q} za+g>gQQ?hb#L@ubHQ!lqp;Cc+far3s0 zY4ffyVI2WlQZs0|jZbPpLMAO~gw=d<*e~-9csmw!qEDixBQW_jKEzM0Q-3SBBdgBX z@G$6X$|5j^0Kv|b?K%$SPmNjkujg&$P-CN&K$$6s?Nw*Ra9UZ36aa(uHsVy40j*OG z^AYB`0=1LuaY8=bAIV)d!ZPy z6kKH|=6o>kUj=y*93$~Yzy?eGG|Cyc=#3Sr9chgB#QC@rbJac2*UC2@#SU)L1(jCb z@^qYvql(L@^)t~68)sx{u>6*=q1~^{Usda&OAa^l^Rm69nuDS@_{J5z^bS5AZiG|i z!hJOLZ#cbfo#Rzr4N?~1PhItHxxk(rr$i&@uoZ!~nLJT`D_Zp1fK$7^hzY?iJG9b&=RP7MS3C}5$hbi=Q#5;*$6oAdR zboPzQcp05XF^E7pz`i(p!!BGDeUjgqh)o28)01FPfLu6rgZPj9I zT~uqTIGyN9T2zkQj;^Pz9^RrJpgIL*{6G!IJ&+MYy+}RqJzbB>LlhiB1fO*c@CK0yNp)w>M$@8fWs{ zIWtd4Iz-_d2D-y7^S+ULjL5xGfI(SBa>*P}XFjX1?NRhPrk@z8oF8-z74oI`~V*O4;sUCQA%md0PUlc=<)mX7`X8<$Qk{hkf zbF?ROIoeB&wVAX9yvBlkR}<;@5S^tNw?$`HtRe~VzsS7V_b!4-|%A*F(O&s8%<2r4wW7@O}M6{l>*4KxhsIwj9} z3126bE7-$52DA#{x1lXD_t!5enMLUyQ}9iaPU>EVSX;0&jI9bEtoiK5hdCX*6H#V` zw*+^6=l6WoE7;s~D_KIJwI*u46@48OXz zPlL{lY*eDs;K{|2}x?S9xzXM^SuD0OjY1Shc zY;>BHtUqUz!NmvMY65B=c9`pG=oGIC&u5dg)(GlzXsDj#{TVg^&)=tj#C2N=@ziN7>|s_< zj5E}7wXhV45meG+-xA(!y_#4+25oce4YJaA5vQR?YVJrB1mU+PscYy$(MUg15$po9 z5cs=Gw43c>NA~r45x!eR6hx(K?^@JlX9(!4jGhD})M+C#!+``?w9mRMSqD_Lb3iU_ z+#+wb`_M3s(Z#U3Q}@<)oy1AGj&dZvw5fOy17* z>jk}M${;#H!wmQK@T7ZM1x?QE_{K0ksz8M6uQlANs3{-aN%r*^S~dt$Bw-u8x*O6q zCUsd%J}@))>mHUgqRs>heDa{*(z+m^Kyxe>a0&CYb>4d;<9n^>7V3os6>=2xyhJ19U?PSL(qHu#OLr7`cL+F3j@L?AC4S&WS2HC)i( zs3mFTt%$o-zoV12*@0^m-ykGL(%mM;7n?6Zjx~Ddg#gcY?`_!L{3hO*y*YTN=+o?{Cf~zdGTH~ zOFFV>mP!_HTl_5_NNVqylJ3n#(<+kENO-0u+yj&_n>E|CPJe^CtcQ`71+HmsoOK~+ zsfC)ElU@C^mt?CNho!!#8`T>5#yhW8*$q$iPDk-YrS&MX@`7_(e5lsZduZiwwm&`4 z^^hg)#P#a;0!3+thDfR6wo)$a5a^nB)B*SQOiP83cf=UGL32o2HRnYLO=F0=Qx1U(;Iodietdhdc5rFLgAAy()enA+mydcB0xM+DNmUg~I#KHCVdSuB* z7-aJ-y`yOCu2$%_PZPF`YQs>Z_r2wgDBRqzLML3SpIAR4EU%7>nVOf)qB;Bdj+~1s zKB7f2ZXJ8Oc3zbpFf4@|)XS}PSHofkZkrXq+5Rb1fnf=+U6M+O_Z&e4x3^UQ$%=h! zE~Y`k+~nn9j@m7aEwyHRh-6oK*;tA%OWI`R$xEB@qr++$fKJU@j6rJAVd1Z+LBeld znC!?$yHMdsRP1F4I+~nO<&XJU-5(8fkMssaU`wS)!f#Ukp~Mm!C%;!fm1}icS|2!M zy@FFMks@sn$I_LLKMbgcXF%^)njbKP4JJZh>B7-R&hxq{0kgx_<$}y7&j7v*XTD8! zm2Sgks2^w1BMheTQp!zX1p-D@x^!N7x;~Oxv3`w-~4IfBNt{xPHZ#Xl|iK# zL=$t&L1vQ`hcL(_si!0&-!n2ySXr>08(`M$VXcd>gTQ1WO3}yL#n8pbgtYZ8mZyzg zI+lu`odRV-fgnfe(k285Kh#tJIX8a{@?JHuUeu@S$(spuv*YzpCTGB$n~||(oROelzJ%$(>3{0su0cH>I6X)?^NL=9Bw}?TrI(vCYLi;OW8wfEYsvRXwndC=U6x53v z%zbVx|58NQHUfvQ<-)b>l1=CwT0N2H0itBs=IO-mDg|W$_Sgi{%iC{*Ug1Y7bTX4a z`3$b+eaRYU=#y7o72fAu=$hT9um57^Q)bxnh^@m6iL!mtLxfz@ndZHT@&>s#tY8m* zz??#f_Ya?kgSk-OO%%|Ej zUK|g+?`>d4VcfBkw))ih3e^akCT@_u-Gp1xgkAOUG*20x0EQUlzq?B+WO^oGVzIAj^_-?>om~NpI zLcD@T?KkmnZyr5LlQh^6%q*L0+1Q9x+vOZv&IG{6OZ-IU7dhyAzUokebQSieGDO`3 zj9Vi=O|W^XwMVmxl*5ZF^e%)8Td!wpxOgN>rBfc}w@kY)-r*{;6R7i!I=THj$ZT~z zNFceST@6&s6iHNZ6$nu$LDxNCNg^S)=nXj2Hx)l{cMZ1q;H9UXJ;Pyur=rB8+`P-K zu=6JQ1U$4&*QU%E;4ab16xx1iHPr^}IczIEq-!d0n&!C4{KAHSxpYK1Jdvs>ZURt{ zGmQnUIUkgLjtS&D!r$-p3vi1gG*+5di97c`EN^m4pU0Fvu&Rw^g@33wV5d}l%UEut zYJ)`w7^6ZiO%-!Xfti}&zg{SQJl8B8(~YKw zv9m6o&HXM|OIWSIhuFZ>P2|mGNaeUu^j6VU7$m@SLcPbEYv8MudjkIDB=FXcrLXZklJ9b4#i&K|pn_ zpvz2{DDZ!85}k*t-71~QuLBU97p?lhi5iU%e?UZqff*!9Pn9$3PD&&xA$=e}l2v`e zFl`I_5uxsTuTZ9*D}-l0G5a=KddVlR#4YDf-a+pp1`acZb41o{dPh3l^s-4TLgdEZv3fC`Pb z92%h^GhW5Q9C9Huq>Heu-Y%Nhp|f#I)fC5Pf&tXXo3aJRGDr0E$b|~rTQ0KcvjuL6 zrk2(m2~=?UEtTal7m>e%hu8kDx0Q$YsO;9Yaj;+qG=s3RnjdAZh?f&Hu><#l#hLHG zwZS^N$QEF^jpI=F)UJcFvWFM$y?h?jj zB7CXOh)kxkzzmA^U~7Zj6m$h;RbhM-Tb75b;U324lH@W zFjBp1RqUGVZcI1U=xssd#IF@+9DZNqqR)QXCu(UbFtYNAOhAK|i4{=AoNJjn(h%w~ z{7x!DUnSt~P6W#bI+I1Bb!emQEi68$^)Td;$E`}MPIzs=k@>QqpwDb4FxoiL8tswX z;y(OyOOxNJ{0q)W1j%Ffdrph^tfo&k7kv#)o&sPAN?3pdDF=^g#cdmK@VPKPmdjx- zW5)1Mh?l~bp!BeHIU4?nkATP&!GGZ}+ytwRu9fx4_lRcaQ;w;pg*rd$bZWGMDPHr~ z`3?f`Z?rnCD+vOK&R1saSrc^skx0fy4ugl8_FtZ0A2h{$w(&=egtT9XNM(^{)+@TI z6#PL{D0shmPx}t(qO3A;r9cS%@%|>bKbI4Hk>#b*j&=2C9zbG`pSXj=N0H00M-8!S zdA^wYm8C&7r`~S%F+=)X-wsFZQVf^)G4QH*zG(v2+V1K$bVOXX0s-l#Q@Fx(KH~^dT&u$OXEKfR;$s93Af03F->HF88D-b&d`!SSs>5^iW5eMNj!je3s zMuq>n4>HF~&x7Q!*XIu)aPJbwN;Ls~;q9OB!51N{MGeTKz!j6u?Bp=6|P*>(dTv>*R;t^Rp}pl;|$A|LEgzWpkT-JPD$I z;5TCPr3<(9masM9hnxM?k?TCUjXf^J$1bn0MbAo$U1_uSrGI>_mNF#`M4wI<4m^2;k;#8ZN~~p;+=_eT(PQC7qK?hcO1`1PPn0!%V+$d zsnMNs)-dkVg*%=7Boepp%B6pR)=x%Y=euiR*x8090?PfAD*QRUtCjEbVXZ-9BWdDb zwUg1O_}Ys3(B9~G(ppZxo^ENGR9o&&;I%O;il+@PEjM%bCAFnbXF;s9SvhwoEkn)e zgW_PrfW1CrXR0Cd#uq#$4SnC-wpY9s@Ig^7p5h1X@!ed^S<%x@n+bs1t`KZTtwyUB z1N$rfSgCEw)DT<;GHrBWbZOuWwN3=$FRO2u`>?~n7`(-D@rR_p-fwCzrtq9a!^REn0xSEnv(WEUytXKFVU)=X|#o! zcY)^Idfx+?p*hb^?_8JQQL32bg*F)vgkcS;3qZ9|VDNWD9+~%x4{JeClM_1E7)yAM zEj%AOV9Cv4ku!~L;0cV%P0|U*S)P`ut$71hqK9{@eLN`l*bM_W;?G8u{80 zQ*tw=`JTFE_B_FhZ&r}Q7qWI0^Gtmt_aqp6b`qxTUDg}bYQ~rJIn$4&`^bR8Ow)xw zVOeZ-_g6>zwNHLYf*-NUDzm2;E7C&EM=${Kog~>==IyL3t}fH)jmIqYZ^Nn-#)&Mq z+@IwqCngrDLt)1!w7*-!2qUOKAi6F+12ZFB%n0`yPp2&EKU?3R{}a3tP&21%yXZ^i?IL zK@eDvO|(bM^6J^K)H-e8zZz2QYvb1vX4UbB9C*5(Xv^j~C7NC!rVA*`xj64$_NJ>T z-mJ(!S)fpdMFSlKkm?w8EG14dj-}$yG6{5%49{*4$XqC8BU8bwynu+|i;WDFT`peU zK|Q-r#~u3dAbl@O{W{ObCgO+_4LIuW{U>W6Y(xz{VeUlwHn}{l84E}bQB6lL{;ZYV zZzc)%mVmEogF>MUz>Yu^E2c$)~{=`EV7Es!N0CEKr zj08i+9I}0aHRa&r(Poj0c-ey2h?0POdj>g;6x%b^lpU|5<3DR5i%85#;RVL7mszw* zYpkdD8QIHt?mZSS9VxESG?Js}=m;Zml{`k!QIPMBiw8A!qwnpYQruekd`i2U&*7`R89T72iaC8zh(|7pc z>i+}wr)T&nA@E>6w1s!}^1+|FkDpIs z`InE+`a}7%&@=wWUjM!SzgtWU?D$MSE;EIS|C?CP!iZ1Jz($WxPsjRCaRPd6C|YqReJgWAejC#tN}l>B2ekZ- zhQ>Bd`2S>u_MapDvq8=9&z7LRotUw?shJZ#3k%D?$@+hoc(wl<{<&j*dg1?)^^89) zX??eUbqPK_{XfM0KfV1gvHnNQ|Hqo=;raK?^{>Fcuk+tx{ZEC0{}AbalKD^0|4XoE z#Ap7ezy53c=aN|1f580zH?jWG)mSM*bA|c}P8cdtu277iuZwL+J0chXUoCbQZm38& zq60Nt(w=h0D{Y`@0xw8Q{FTUU2EV?4}D=)p59=f zG+3k?et9nrKzOwa01YkxdJkQS9~}h9E|~uxT!ByiKb+9OM!hz4(0)*)e27;7&VBmv z!JHl(1C^DC@b4ezh`m7ipmvaukX+vbIQZu9&cXb>Z~&-u{g@WtwL~CR;IjdOdUAhG zeE;YnHi_V42l?~4y1L5Lc0rSFuAPCNJNj|L=)=zbY3Iw@%0=_rAqQ6aDVfuwa6G6F zWTrWw^`$i%ph>vdH!as2*arvZ&(X@&+RCBE_cPqk0o;g^2xt*47s5L>?u(QS{6zuh z7m;v&ZRhL$4F}@*GVlw=zxNWS4=$SyoYIeE5)Mi(LGf6~4L=*W7Y?l_Opg=&Wx&4I zMlTg-^~3*WWUdc@T^ttRB~Q%v=mf|;cl`1P#^p-aNk+w z;Gfin01;4&K*tu}JF2J%yWs3%9BSN=OyKCWq@m9<4*gn(dbo1X&KN)jpC_vy<}0t% zY@DgUN;p?Q@7_aPZroa=zKz${P2h6^R(REjDIsegj*u63_-ccAQ6JJlJfUa2n)v4c z{+b^uc&vE502dc9PlN7eU%>moZjRiei$G_$BTm46nQ(T4f-t>;)7=1)t&G#xBKP-z ze(^ohzJ!1Pa&LIVAOPcEQ6RXf65sef0CKOk#1KFLOW*vP0J+D!q2K_EZ&;9!Kz_&H zxgj|CzC_(Sr~H?@J=8svAdN4qFTJ(jSa;lm4qmO|d*5`ecyNCq-ZT1!K6kHgZDXMU z9)1cF;Dwh4xy5p=uI*o!f6;IZF0H-hLHdTi<^g{9`)aw$m4c%LFmi&1gmyNUxo>%> zlZanV*U=M(`=mOQ%}miN=mi9wCgHGZhpmiHrYce-WnAvXGuHi;OLAzR)X3zy)u}0} zxIg4R$xYa(&u8@IboPzrO5643zjq3+MI=|HRX+=}t8=7{=debRCv9#GQm#Qm&YNjS zze)r7kuj0j0f%B$mY$}|<6(>)FbTH0l%<6dUj3fHcF2z!E|lL{`z+@~5U-UMvMIuv zb@v84RFSBkeSf8rUgceeLn%2yud^Az2qR2{!_1D}7^s|0pCEpW)3|I%6Wy}-ic{2C z&S^FerhBOuye)Wqo>bFwOB$5;HL2$tGp%H=pifhy{O{kh%qYelpa-IC{7)kbRqv%qk!@l&Ky3-=jxwsm5%eLiK)Qrq9CX@J? zF=e#xtnP9Lvy7uVvD?Wb(lPn!m>et4Z6nx+a;|cTE_^RglL7n?=I(a0rALm+0n+=1 z_n8_5cCxM0>FQ2;d8shzD3c@)M^8MbGtqRPJb(LQTsAP|Q-Z?9|gco)i?W%VBeDb)T-o+T9OMgnr&PG271TK${f>~eztEsF$=H&n_H-;MK2 z+Nt93&Ro$2a$Xz2cbYLNh1m z>>o{biguYW!w7=P_F z5LI5M*>{17gH*jcFKMyVPy|#-K+titPHto{ph))dm|QN?4;_D1YF~l%g&+u;#7ghW zNAbk_ofM(MnTIncEsw-05qjntjGrk>1Sx-AAnBBiL64mtmrlPyjgQzJ( z8D57Y=NS8t-Wg?HA$VWKz6D{}%hvIReMT2`3!an~Q3tW;7>S@wUbt&o33eJ@uRqad zF;54M^Wl5K+I)1r!{O6_nhVINK1(ckw9Z>be^8$`uCo7W4}Rwbki%A=snJ zxw&t$WMu#goyTf^B}a6rnG;QYn@b|rd1(d+2Z{Q}1OqPbt7iE#?F(AdT#=4wFPm_|p{1p|Z5VXwZ5#Y!|X4N+y_!V) z$L?Oy-!DfdbY&uWr8|o_!!WE;IWNFWBOUooJR5~L{}+w(CX2Ym?<&f^DIuvWH(Mu$ z48i0sU8!LJM;@i_l;#jxYd?`xo|j_|pOV^_J!|RdcfxpLq=iB4;!`o8a(fH@*}!(- zV0DbhG^!tjHo}m7zt2s`%mS#$BZJsHkCj#SO?Ku+ZX`RIlE0Z%+TKn^_v7tD5{u`z zO0S6E`iuL`dIP>*a$g)uAic8bA22NxIpo-}lD^My(NuIu)#Y9M7) z3f_pahVg|72%%76KE4})vNId97J3jc9rhFMHgwKH92XgD`}+?abL;+Pk{Tw>OG76P zLxepUjc+w$)|?Y|O>fyktQ=ZX^E^33VdW=9A3IANdcZ|J&Z%c=if>OF-6FkN|1ulY zS20PZ<7Wardf$>AJ&@CqDkU$sovf`s=;s&VPn}Ezgn|Cbm@gboHAOg6Ex{y0G#y28e{we_9fGOk+1z^ss<(Ae!(!xy*@%R%N*Y-* zvQ_@*A;oBx>;I+p5mu)hqp|M-qShK5S_L|wOr?CCzTt^U(OUA&=!2^~R#RY^^;Wj% z9;wJNW&?4+sVDFy;564nmJ_Vm*(m#H?%HE$;gvbjUUGlKDB+3X6ynl zUs!Sdo*)rIvgRQ|!tEDi-8Ym1FJ+R^-YI;tfYOqy(v$JXe7VbjOA+!WBb9>?F7%ZG zbT?I+ri`mA<>oU5EBzB-!D>Brz1Ns27;zb8d3Ws_}`A@@3wyN-Ny z+tgRQzndlM6Q!aFRUT)`T>%{A=9qk?5^Dgwlt2mN(hg{$X(tv<(6;gsYb@amFxFu% z3EB)QC`mz&f{Nsj4XIMReDI7a1SwO53Tr`iiX(zub6W6I0sWG~bOW zmviQMrI<K8UN%Rb()Vllj)O7b|g_tU7M@A`x`o_+-ayd zdHj`ib&vI<4P6{$(7OPA56cZ}gv)Ay1;%45je#>jQ$CWH)XT?Y&Nnea>x}_r&F$IE zQ-+;?a1n1rfJj~5zb=oxZ9~t`I5>V&XTdz;flzlYmSKCwAsj;a)ZO8i?4MN?Y>q|2tQKN$PtDaVCF3Wg z1FJ(;+orKU+de7Q0_2N|4g0B**=OOuE8}-uHVcUXCJedU!;KG>?cT#6*m!Oan<}Gd z+FgIE4nnTT)w(#xSKnQ-<&IQLZoxZHKVz7n8BD@%HcC)pVncDjX~k3Q4uy;6e{Q7{)7oCvGXDSN8j4N{7N^jVHdm?Fhfw+S+{l zOtP6;i@nQS-a|njuL-}-OauSb%CY@k`Cf<=a@kQtnymS=Fp|K+UCK1FU}sl;NFIbH z_{%HcuqwB!aOGkx28JoZMLDkIyv!#N!|;~mGFr0gw?uJp&}B;2n#pk^dO)++fJ@lGr^>yed>@3VszRHcUtN!{gMUBa6i`iEA;YKOP04@f+@}mn z=<{L}n-`{eFS(tJLg?}Q20}+(rXNopo){ZhC90#5r$W2Q_2r{7x9Od~#Ne00ZGLE( zFY5;s*x;2CWz+Do{!APp_POwV`NR97P&raK>0uJG)XdNY8BA@)gXl`WXCPqz_x3KE zkp?BgE=A#zkm*u+(O>!~9XsvG>s5N0faOb)PXd5xj z)?e-WcKun_N|p<=lIrK;$JDo?eFT19^G8q?4TY1;ka=WpD6MgS9Dvl`v+8~WxyH;^ z6x^*DHdjOA#L2;})s0{gK;EEP-!BEhbw7^B;PkAwB5&v>UXr`B-8!Mu__7G2EF_oM z_I#c(7PC&Fz>VV}Z6rn|lv>Fgavid$yHr!7Oq=8-`Q8GyGneo*%5jn_xYIB2X1%Yk z=+GKw9JJ~f3p)%>3{4JgK3l6iTv)eA7J#Ig2S`#P9BD77Yq^bB3N*Q`$r@i@z~!S= zMd_rc5#}ot`f9w`t|BVGoji`lAQ#~xdH3*< zi+9;S_>E^87Gc+6&)XEr<42~P9RN$6=VCVp_USMcw*8@|1zHJu;X0A+YB?cpGKiz*2;-+%gHil zy#+%IaxwAsP^?UMWfO0VoU`fZ*fgxWm@Cqwrd-C;!qzdvw!MIvjs98SYRe-B(Y_{o zqLW*$*Gmf{DA~2~mwB+-BjlbMci-cDt+3#iS;r6IycbpYB?;oZW~V25YMGfZ9D1Mwr$(C?R0G0c+<~5``LS+bMCnB$9u=Gg3tOGeeLDJ*JXK~1p`uZvQB#tHC(J?kjrGuvL!3WTQ=ED#n~NeI?oqs(?~Gp4YY$a2MA!L+Az-f z@Ap*303dP~i}QjNKY|)jR-Dm(ke&Joud`^M71zl)k&+UaT4%m7?Ivn9PEOWDN-V|p z=59!!DR zK^2pBQZh$Jsgd3xuCuN1K(bM}klqWQDxw>5&m@g#Jqj8PssV*2!Now$+d#o0s>ka0 z`k}Gr`Bb0TL!Vmx9MeBjdkNc&rB|(2#`wvBPbTbj$jM`zR>u zERnRTjT&0^rZg|?lte$R-h9ORo|HiM@yM8_^X0`20s+F9je|aLfmV1aqa0SEOlKZ7 zU(j0-sE3DGK8nu0*!y`cFD*s6f?KS=Uw|wSB0|x3-2>tQJ$=bU*{!E#_twEz^c#eC zdRanv49&i*{L)$|4pShHynf3vrDG&o}8R-Yj3jFVg)xMjdM0%^j-z%QJGj0%8mI zn~XnVkzv)Vb&F2+yE<0-XT~UfNeS3m2k0U5Yu6jJvk^wgSMo zXM?`OJ z&&Tso)d)?#GwOluD3Ubo3Sg$&`nPo$p5cP&nP@X3+jP$3d zE1x4SZ0T`;hM+_0uW0UDyKqv?-I?m?_UK&5_os;5ycf4KyEO8ZD5M% zGOwO-)P2jEEcCFBZ667*J(BM>GQWTZ8|&Gq8B93y@Bm4R9902bs*7CkD{={Q_II|O zk@?l6$Eo5BUxO0m$9<;>*LT3KY)dojwB%Qe&w18cS^Z7Cj2mILoibLRiqpEN%8cNaXHzi1e$-M>7b!P5 z!(+n-j$$U*lUU8g8NQihOIC;)1V4GbU$Krg};~iND74>|r z&+z0~Kr*j)POEu%dp>vX?JD%8OLHfa-5fxVW@K>;}B%zO_)U7C6ogndV-hVTf`3geuQhW;l4UVPbsC zVqW{&)3zzKnJZbsL=L0rjxAVFqW}S0*uU*IY)HagMqViA)47Ii{a`U-iQMYIEx2?9OaUs zK3Y&;P9pp_z7PUoNF80FZX#g$iu_V%qBWRX9wtRkC+hDv!#;jzs;uUa_Cpp_o82{w zV<$ms+258%gnNVc2Sif$=#9U*qNtgD{^p&`{ z$R`w(khY4d{osFCWi>VwDIWUeJVzC|t)7Z{uQz7#soC}}mw(5P z-pYlk=%b}F-X-H%>K!}zX+Ky}*^HV_Ebb%Q5k*?WV`oQeX7%YaAi|m6K?%EK5W!;G zfQFQV|2!0pneqx#DfFQ3av6;Hrc(MEi22)anD>?c`3}tYl?CUN%7gIt62^Ybs{elf#L! zhdG~IKGs9ZtiV_y)57mLR?;AM1fg8$>-OdM`@2gzEdz!JV79UJQyLe_d#%7on954E z7cgbC>XtG@6{D*!C{B?5=~(Vf31g2!K9>S*E^|4qkGf)B@ySPDWov@gTX5jhj+l$3 zFpaxOO2!9&PwK_jp#kr@8OzX}mYJ$xcjZ#AsfkT<*AxS*yArQool+ zEDir)%6h=aujPn9qS=w%<3`9xhud8qBjOlk8V4PKS!=%p^8&-(fSoc3CNlnB2y(@B zWn@6_C`_CmiAcP|)lXLTA?fUTH=at2cYzZCEZVK<71ZKRAlsU;cjkCREOK|%vSum@ zTZ~I&)J#M+WP946fjPwRq{si1QiL@Mbb|EMVvy`X`gLIP?%7hEN~qcUk4@JKnA3wY-6sKBjF@T+HM1~{>*hTt!*}3pJn)$$e3*fC^_0k(amW5DT$!)06aHDZT`0QIp$%`OfK~27svZ^Xs%;1 z2QH5WMSoJm%7eEAb9WT$pjR}JXuP=#hFq(VE+3@CGRW!za8THgtkj~(R@|!*m3$nr zn%+LwHWs4{za94&20fBQv0yi~bTj#C%a34gpt)Cn8EXkw4UNLl3L&0`+lkG4jxn6! z8?cLvZT`?X*fWaJ!&wt&tt2PGGSg#ica5(+Gl54qPvH(*HVUkab;{N{j}D>AP+|xD zmq3+iwo%rlFQYN^sVl4Dg^lzUYdxKi91h5IRBCHLk3h9pNuUuAJsLlR(11}7Z^!cv zB{g(Gj|d}g)r&J#h*6k!-D7$;$*BeRGx_TIiBL`g(&w^E#clmkLo}re1i%rGW7Zes zQdiD{c?|IoNvf7ms5wQ3tx7+vi8Oaym@XqY?%^oqdO4j}laoX|v42D?@fpW*jx;EI z-~loyU?;47)DfKBlc&54*21*!VlmoL@Bx+rOEk!Ulq8=dK%gXBlQ-&tQycbi_wE5Zw6Z0+Fy9|(hkh&yL z?BIr!Uk2PPA(e22IbRsJI#(&Xx>@U$vlUMmb^%IItL2on_z|)nW}KSiJ_18IiW36n zSAq;!5nOEKdhu7RSE5r3QsI|0ex>?hrDLm_3)LsqBI1s7XPvoHj3Q}?=nbzz0sCfh zHBzHPn{kmr_SXzK{|zN^(zyFp1}j~0Df*H=OHQGq^8S1KWo3ULHY43wKl90g0_LAq3+0 zQ*&I9^`1A{zkS>Dl;NeYi&n@AAoC&*4{Q3eiVqty^+N57Cd~2?11aSzDG-9JJu&vckC|q(~F3qMtACjVHZ{DSlTnm=z-=JvJI~zW2 zKxnZYG^E|CfJH55wv|%vNbA(sU z3K;Oo_an9otbVi`N+Qj>huyZa1xvqK(-rx44J>88XeKwW7;7kIMabEDf=g?zd8j=L zk>9$t{hFPH_FZzpw%u>Vu0um|BDESO0I?sl0}i>ovYG`?7)(G)tpBk=6xYiGIoI|@ z`qNXWw8t)!7)jK1e)D=40MsEyWbJEZaUbrCsHB1nzeX22 z3Vun}B0tRPx%7pQV`SdXtJ02e#Z3J05P^!W#1HMKv$3+HQ?vGLmH>7nEi(PVL5a-y*T#+*&+t+;8wOYE#v*&_t}^t6B$Na3HwxPJL+^Vd z6DOHm75ZyQC*t&w0dyEBS3q4WCt%JD36fj-xU^PH+>^I70}J?m0ui&AaeNahCvUgN4rzS>5%@`%F<4iRCVgw9lhGP)xB&F6G+Tt=jkO}Vly8mL_x%OFqEJC^ z4e6$mUVAJT(SKTp|t69Fq$V!=8AY-7T3#<>}mU%up7|B^tLB1o+OLx?EGq7zC@)XqXwy@X7M2hNUR7g(;e@rs<*yoFPH2 z*=cfmF5+#mL5{PEv;qkntw%8-h^h3gF2}d#X}9F8MQ(M z>ck5e=X-eBDofL3)JTNEpft_(unh#to6!B6RnTZe zq!}v`z&()1dKeV?4(qx&0UB5>n(8H{CVrlT&F87~p|h9epWi&kZ+lW?hvy>gBr-R4 zZ#X>al?!Lb=WO%*(b%}sbIL8@`X?~Kn6-&|-hx@57^c0X8ua7C2-CpBzd&mYT3K}r zxX>Y3Z|ATMPD6N2Kr03{7q8t(8=6{6blJHUgtk+y9Q5VKKGL)4nRoM82wwX`=ATV; z#0qOgR{BiSSF6ut1r3YS7DjLedlJ{2C_lybWE^GZLfwU`bN6$0G!0sq+cTM9=-5Iw zI)SlIU7R=PIcLEzXsM)5RbJR0tr5E~pwNojs&*XPREN-*ysAoW2r`+fBh|A~ zw+=N{Ul=af@DCUY)~@D{G2A4~D2E_;!Q(-tlg5)YV-NHJIR%^8JPY>u6x`Z*D6yLB zQ_JF0N@_uMwKz)E_i?R=rbVPapydXP*dsIKI%9LjwR8m-Ar?St>bvM|Z6G4tGHbTI z#rtl-(Y@7+c+2b&H-TT!(-awsIMiNYq?oeJq_AR*<)J$OCef_$XtsrgD?SA@FZA0p zXb?n&WqKw~+J6t??l@c@a+n}%G?DZiZ8|VeMX^TwE>?w%nT-+mh}iqi7L(2MrZ);> zlrmXrRb^kY^;|4Z$a`F^mNx!Ik1qo=4ePrfb=nVG--H+vl?y~3c-bocreXdKx-TxOT|B!E%sY&i`w8#-H{yEvdUzl2L8h@Q5yLYjzL zbiqcgW?o2JUn;V0$1+HYp+*|zvWD>mZK(dTE?(Q z2ub?9*&%tc7#W*Nae)WLqz6$pte9#Lgw(wmf6xr6lCN|W1 zU8(o#NQSS#zb*e>^%UHfj&x}kD8z%Fhi;`XyTs*Qg;iZ zJHy*)(;G5+3P%&HZnk=CnHs`QU_vF5p&SZ=#`<52v4?u+>HbeNO++SvA`+Zn+P#r< zhS?I28b+si65n7up%c1<7d@?f*4zOaQpTfIUTQ(1>gBX@s0bPK=)bbNm=%SGKF&RE zORD={raIKIF1A&_rU+u}?h|YrU@;Mb6Z2nIQ^3>jP$Td2b3G7y zIxrr3J#N0jo@9MUKi2YpM9u-~f`M=lZr*Dfsil1YDVX2^|4+;OmlyqwNa>iF|Dw)+ zY31K&^!EfC6Ep4K-1N5`{c9=X-(_^n|5f&%)b^9JI_TQFSR3HcD0~h-Q7gZWoxyL( z`Uz!!f295&F*DO|p89W?`Ck?POEU|a7#bSb8d&NX*x{++F)-lKSeyJMfIoL7Whpn0_DHXHEa2&%alc)cG$5{d?wb`OL)f-vsf$pVmKQEgn7nZ;WeVZf{`wIX2g^ zHxM-VYe%0T*UsM7K*s{o8Bimt+p-rAPVmCLA17A8FP(wEL{8{BTS~DJ_ot2pem|7z zj_*eY@0!A~4(!;Nug+e++u0Cl6fTx|B!sYZgbf6xnjavkbPkvA4f9!H$?~!wKEs-o zL{D=r-A_3nUQa`Pv9|85et*D7q1I}Fd&cgqml@@JExop#u(&LERo`AZtp~&H)?|2n zA?ftd4(U~Ld9UC~YJy$iWMkZTbL%ESz38t}WD2v!4(+DiyYDI-v|(=7Yo>Q-=4S7U zv?P5(mi`e>&^mtpD$M z20Z59cKz@9KhFA(v;O0(|2XSE&iaqD{^P9wIO{*o`j4~zp#x=kF);ctp7Oc zKhFA(v;O0(|2XSE&iaqD{^P9wIO{*o`j4~zp#x=kF);ctp7OcKhFA(v;O0( z|2XSE&iaqD{^P9wIO{*o`u}&%`nMPTZDs$%S^qN&`rn=PUvl)nJL}(?^nXg*|5s-% zLMboI|AUT}jp-lI_Foyukj(#x6aK%AjsC3QZ#zx*n?nDMrx{r3{?};ce}y;y`xGnv zZ#4WF8BNEE$NCvP{Ck?|Gr;-xmyVW|;s3HKCMG64W(KCu@N3q8j6V`CS@9S@gOeFQbFhDxGt>U2?*AD6u1n+J@|FK;q5s+F z|7yd3wCTU`=KpB!|1C+Ho{1HYnt_o8kB;Rth?$M~zeh=b#zp^EkTe^^zXwS(d_wzw z3zBA~|2s(fcf#~PgQOWhgSY=HNScoJv;6NQX?j|^&nxzKk~H%_GNS*LB>h*xKhN}^ z!ueBU|C?a`Y{vgcj{X$Re^LE^ifBAKHoD)3{J#i!$bT2|<}G?rhJ3Y5E9WMwt}7`G z7OM?+bxanRvkhk^s|^-zD+4SCTpMgG=i!(y(uxZuBk`rSgA+@NyM5dDyr_!OU<@Se zB(B1iKSP?TNDv88F@Hv9e0J8T-Zwlp)IWklX1c38@O6ro0i-C24In8Aa4alLvT7Fq z-VIEeloCn;6(Ans&MOSSL5)zJl7o`;XG?o|dun_z@z2HaajDNfuj1E-<1^nkh>#f= zeun0wcdd;qK;z|P6%iBD0A|HRDFcfp0gk7InFEGY3^XW_0H{zRGGx`FGj*-4LtIW? zYGMBj=9-#2?qp^ASX3pr=Vf6aP4w#fj1V{ju+=T$--f&{0ic5iRfPWuKZS%x`DDdD z)t~%=yE4|dw9}dM0%85Mkx)MUv1A_p(RkOH`yJ=QA~(=L7br*%PS^W}p}cb72l}(f(9>sj@I24gV!#5!{g0`NhK6flM?(M#csC9(WmE>zpK0yI z$%t@x=2m8qa5vU;z$q!o@31RF6C(>ZAE+N*@N(aTKW^cGRA1$zt~xb-u!X9*BqdX* zHowNMtqt90Dty?K>)da=+-OC-yWR6B|MYq>HHEFad+oe^2f4v@f;PRD6_O`YmpmjCv^K0i}N)tO$vc>UV07NKvO}D0As=Q*4^CWhON*|vLyMMobd{u5~|d#tkx8W zKT`JdcJ?~yJ1sKXWnabhk}Ujvo0c8gW8U-ka(vcrR zK8b?tYkt~|JGvllCs`TPG<*~Gj5OCRvAD@ygTB~IPPM1xYt=oL)l$+Y^`;=(-}EO zIlffG54u%5gOGE;sc2AE+ZVYSiytDP4O5eu+7J|~HQ!^tIdRIv)``XOg0eg&Ft+7h zQf#bo>y@s;BCOL*uRb_N6R+xY54@c^aH-~@gy0;Q((WR>lIUlT;!Xl8QZ`4CgN!~O z*&TLs*crUWb@7&A3D=!>J_(c2!0MFRPe3A#L*rLgR++S6!Ur+MU?sref7svx*xjmuo1dRS4T7G&7=fM zHo0`mP;+;vPJ%AcwIE6>=@#FaNm=TvAp9E#B;!9A0$7{0e@t)4C3R z)9_VYWCe+r#V6ZL4tMGB6;Nx(n=Rv7Tn#iZ@LkxLYYSH=a`;pUbPzvDDuO;c*2u<+ z7A|gYus%UqiF{XAtoX(x6OFc9W59A=NCQ;dj%%xgeXDy^t0+s zP?+zq;b$DGG;GT{kM<{SQAw1%8kC-IL?(_WvX4KyY@E@xqB=kO5`M{b)~@T}c8+em z?PYz|=z>k&BZ<3*i5JNhejm8J?S`&UV(183dV?;#(2evU^yotjoq`Oaxs~Uk)(v2JTQ^UoP!t z006BBxDIwuP)5flsTMY?fe_k#IMBo z?{%cMYC0OEaGDAHdCsw+v^b9jZ0S_fw@Z%ptT{<@LcCZpq@+? zYYh)tIN^Mvh}>wdI(`KPadlaL?T4hGb5NW7w(K`d@^b7V2*1PdC0Z&u=Gtx*Xe`)I zscma0G^N2Bt*q+usNk8(Si{$K$VfHAI6;*QG8Z8zM*F(hpZX!7p4m1!k?!q5ytt}6 zgeim^4bI%QN8ZGT;l6~)-3MUj!WN>a z&^cFl_5!N}Kcy@K;AJ~1%0Ak}z$K0(L*-57ThOV!hubwEvV_RDZ+pOS z*`9I|!^SQcfR1EPZ@(FcqPM22(VX4Ee8UOER^ZDGMPMg)T%4gEB%4kSh_Ny!Kc*s0R{(+{CZvbX~@?ij)T#c#>}pgtD|D z>x?cbHlwFyIYuM>COeja-ipQuf~K#)Z@4V&DZBkb+hAe5+gJlH4*6p@iR6K$Wl~Ny zOz5Jm_JO=mXF$QIP6(|qSUdsN$@YZUc|Pe!*#PpuG&icNR|>%^d&flyK+z2davZAJ z9Yz$!5?+An%ap{4kIc?AbLbDy*}-$49PuU>n#l1fRqOI`dw#%5YaA7$*l6``7=iRP z_I+aZGP8S2E3dHrk$u)`LiG-$Nj)k0NIw=SuPL8MSv9R3|1!?eMH$Hz)i$x-y=OvZ z+DiWPk`MYq1$Df;E;xcAnki>2aY`7-ijqPn>J=9qa(1|BKqPV;qad5ogRVHKjZLzNP#l+Gh#JV;h6zBVq5u%m(|5vdJ!6Ek{4z z{L8eOT&?Ozp@Nb&oPbB+B~r2Mtf%YhW%~RKze8-X6+%Z(LU!&zDy84{gz^(qFBaxH#38-~q&huI7^oBB|n#dHLehAQ_wG z2#p%p0+NcS#l?^iNF-v@(qY@Z{tcD2}*#PLnR1EXT_{m7D?nM&l6{ zLokO>!Xi<^ZyMg%oM8c`h8%H)+@BnR#&#m5e|n#HZO)5ico|z$A!sYDsZ6wd=ofH_ z`j#BAiAQzi9NYM=M{r-h5*Bh`BIoLEPbH63HA4 zBATHvs?{Xr79@!|rX#-RH)qn!JI(c@?S&L#Zi(7Yc3FvKn^9t$GMA3=RY!_AJI~<6 z!gl>p9hz+MR-u|}XI~;s=@})&cq{+?!FgM&&B~pXMso3#=ETa*y9Hu*42}tx{EY3b z%2HG}wMk{E5ExWEk+QWQQ*Hz=$s<$L1@9IzC|U8Dy#5hmTSyZ;IkwuoB$FcD(1aq$#lT92??LJJob7F3`e&$uYR4L$D&J=?< z{0TyqX>v^hC|aUx*i(gE6sw*Er-;hO#$qM|gXFPl(_?lbd;HN7;M}jl?wLh8qbnbx znA4BssI}bIU4&iBEQ=yCeO5$G>`{B(=uCE54kO%&3mj$%;XVi@UGdcT%YmqQKyz1# zA9=xNN-zDK)VnT?`}*!OIY4&!id0J~7d{RUv_uE5J70+h&c(pK$H|$Ba_<7{+~?Cy zJhyfwO>gT@-m9B;*G8rS(xOi0wS;tBuHyd0K-8hv888MPS>H#UG zlP}Wno1A4&mWc;CXql?^S+^d|bL`3F7LVNIn3@cBsLVzVQ}Z#~1AO50ri3I{X)##j zVm3y#epz#o7OrNHKQK#xXP?=Zh@=?iDaxAO_S1_pu2P5a`kA$G!#ON4FRV;XqrrsPSqkse%^)~B~M4D8joBBQM_`Rm7zN9BnYB?5}qAWEi6i0-VP)ltea&PQYMw67d$3hxjKYD3!HP<*2t2HJ_+ zy15f^^HHbCzc~R@?$E{8-|OBV_*sFO1g=0Sy;$g<9!9_cLHjtxqxw{5S8gk?`Nadz zR>TB7UpS5t$JmcUw&ra^h$MM>FXD|Lfkm5e@g?iK;iCxGp&U`)%0@z(j^LmJkCLz% zs*+YZ)WZXS`V8MSLD|HIR7X4Mx4JRhy)#$qXwW=FN1*wX1Bi1N1-;rL$S58wzm~vC z{Thy{93sBU&SQ{)Y38>7sA!j+S(Whf`vn4#-l@C z2y767aWIvvCQw$i66mTSjg|ifUs^IF|6>K%VVK zlXLX^9=NI{J&&zlQTYR5UjybumVQM$zD_X)yIk%bpIDv2q*)dq&q5GW#% z=(9Gq$8`1gwr*BMWu$!Nn|Eow@6Zz1T%EzdyfQUzLz}Rz_wU;LN{H=P>0ZKx4&aD9 zr=WFYSoqPlmE(3G2pKtfc=o1&t=sLVBIc1h2`=ISOKTrAUsARd3y3d7lg=Hvy{Q)N zhi{D+hI`xphR=ZxIBnqJ(5|DSh(CESA>A`D@ga3d zH6)wz%%KiCeqZt|tdj^1z>swv8YNGz{E@A@9Mn-Q%QjvA8p#Hd;u4RfgDIV@lAg#r zzbKa^zo=`fVC4-vBd2d9%VK6lCn7Mw)Q{BI!=)kOwHSYcDExLlL}i|xiFPu{X%CCHvJmNcd*pM`WukN` zh;R>Se;k*g3M&ktVZw;|)QrSN{s3ciYAuK6+Ci?%?8}nX7G<1rX}sqP9p#8_$ev50 zRqv}X@z9{BCEphG%mCJ7K2TZl0vh)Kk>XO#$onI1ir$Lw9WLAt`T+p zmSklS&(m&oay&H`F?J65KHxgupvek z`(_52kOgZ53bwXL(S~LI2qY90ORh8{$q3k{=cWoORKlSGf;4x5w8)svP?<>5`hrjP z$TxvL2?^KOH-GK-Mi-6KMeen7uL9qs+_u!l0U@)anS1pMr<_NRhjpxe`9s6hkpA*g zVwfmw;&E;|avNJ;k5mZs=x0r;xN#kIYKZTe$o#k4aUL3?CR!zdz5x+_9-?S@Q*qm&%4Ybg84>RmWs#|g&^L{`1VfK7InAkU z?p}NzO+LSsVHlBIwKQZI4+&vVf{TkN_l?4zE5v|r3nhFcHDIMkPyr|**TT7zwE6Gw zCaV~zMAq7c51O^pyv^Cy-L(m+jmim%5%6$KR@tF&CxyBwUHc6pugrE8;G6naRON-* zJ5&l8u-_J47YngkONBVb#|9;o=d8aethcUYU|3rp7W;F#Ws zI7G)rdMY0$RPij_DL+~fekyj;5re?T;Y^=v6g3aQ-x;&vY|S9WkkngL;;=|Pbm>sA zpUw;Wtl@s2P$n>S^&Rnh@8TV0N63?p^<_Isy@qSRN1H+VU{;a3X1cP~mt**C>4Oa( zeht**q)Qmk=RMMq@i9S9f&j+4r-#f1&;#!B3>y6jS>8oV>^Q*0dCmblLJ(EJiA;BE zt9@~jRVU+{Nx=M?4SGtO#920&QW~Ib#8OMVLedIs^N0xpKUy7x67tN<*DL}c=zD5U zupClsskZ?#-6cf-<)+RCv&`L(f(Q?er?;jP^sE(t4{j6!ZSa7I?lHEsIpiLpnmT-M z`aT@DTHa@DhudNFO#-LpMghl*b30;V&kmuZ#Yv42_&vCw=vh6+u|wF}vq!@A(c6(E z*(qM(g~W_pc`iB-kpwYbp*@cv1RvAeE9LjmmOZRoc>g}XJHP^spDkVt!@DcnDjQm^Q8O_l6mf&e>W0f@o~-M@oyTrv0^3d+4EC-UGUg_c_qn;UlD z+MfDcQSB|q>2F*EBuRo8b10O08>Rt{wTlnNlW$m|7&*L602w_sFFnJ}=2D-PMd%i& z#Fq4tJ&_YL0bWyOQqVwh@o}wpwMCku`D%IV66jGYG7cn93o+*_e5rPECF$Er%Zpkf z^$W7D*7o7lyyq10oJxHNAlZDCWja#wM~$wkd+rlZyBK*hh&O z3tS`FvmeVn$-A964}>;QNATt;o7$kFGB_F88Q@mv9hEl8G$YMZ;itRl<|FkTiRhJR zDD+w&8_t$oae&G<0R~~1XfBg?YdC~iPnq>?BIEAIdw1k7%>mzcP3x1`wF}R2P->aX zI9U3{6lykzG2VwecV$pZ*!0W8ee=U--U;Y=0OjHi-vofRt@V31N=sQqzukamyXipo zy5njoQWQ0wyT78wI|c4WO+TrZqqoy4no!By({A@!jq&$7jBXonLlrQQxv#)lKF=oI z0+D!wS8|bKxsT$;j;ih{We_Y2Y@FMgOMJ(C75PDc`~CRa?c}f(y;3@raWswLP==uh zfqn==-LeYVqs{QTgQVG&dML8rP{J8gTh(rh2Bl3Z(}q|q3D3nTGiKT1ou!)mubYwc z@|cWJ5uC3JFf{Ojhz^;FU08OP;#1RRp7k(HR^}i!zZ_1ioOWq>~#d>|JgF4cow?_%%KiOZTuMXi0Q|>m!!EAr|3) zx&v}`L1$H^iQ`5*obJs16GejZ=4+a}=7ATI#Ibtx#S>!^NU~gS9M`ED8>khmVNdT~ zaqryD(PrhURwRTymIoiJm4EJ2#SD-=2L)YA&lO?WUKxvSx`x&lup-Tt0Aj3Iiau+y zP8!@-Ib}%c$M(xOnKtr>)5H{<`-=? zZP*TQ_+X0eJJ+Bn53dTO@>V}#JmWMfK^PT0191fQ2EYgpX%}9JuuN^#s}_H5dfhG1 z=isZ6W~m4!7Q8N^iaK;`Lhmrl<@ecXMIKRf#-wZ}8Qr-jz$ErM=1U01)-oyEhpfrg z3Db&P>QX^F#=~=pZHM5QDhA?hZc1*;StD9rU95(&r+IL-eI(ITc_=7rJGFoXB5kgu zKYZCC^;-Z!o;XP3Hw$4E(|(4&&GjCn%p?aGnh0CKJ>lT7zP(#5&}orw&u@O<(p{#S zdzMJZZZ+*|BgMz%{6JJ62M;rE9v`5^v*}TBbf{9Y=r5SK_}%> zCNf!y0@fNuY6s(dABMYGCWhzr@Zel_P39?y(MNB!Id(0oA8_}JD#n@5cG|$w)Y{|a zuD{*Z=r?OOxAu1o`}9<-+_m%c29_h^d<=$j2XbpA4f!>y*kI2|7I|t##{hlqwGZNmpqI~ zDqAc}NyieXkF(yZG=QmHgQ%=z>BER!>hh2~j~l|Spo3f6%2yoF+Lr~fotV?(EloP1 zp`3>azJ`7HB-u$X2|G0`>DSv?b9+nU?d2)Kd*pZr9@+e$i8*|jEw(m16BM(lW} z9)m%S%wJoEQk_dgVDvU-J5ks3Sq=oVSubjx#I>rYgf|KY>1@r!Zdt+OYDq-2(=?a_ zb4KA2@1jt@2=T?9@D>pJ?U|gczQaH7=}ZnKWsk*%&xcNeOg`pPxg)3Y@Y$2Fu{yFljtUCM=b|dS2I29q_7E~v${u-A9`ZylRa*mO!*uhlDm(|%g zC8tve$hBnWyq(gUmyAzOc?@L?)}TE3{!7%ES<7doZDeuAwzt@1UdnpFcPaZ|9J|4PupOV*81qO9oDT0eK4Nn8Qiaa^TV zl-|*v%$rH}R8~?pJrMKgvs>!q4SkpI%f6^i!~0Ih2J1A1Ie0HKqTlAnKju~lm=kVc zHn#j#81xE?ZostP{mt8KC#IlHU4eypCBlamO`@uLEYCKB z<+VHaW5=)>LP@o(q_SyUS!=RZl~)y!dK@h5E>jzScM19+v5baL zQ}(>OlGpxt*3^Lh8AhCMy!>4RGuwJ*d()-0R~di$sTD=o4()jc9bBbF(UMwPVSbjT z^);AqpVe$$ZRFhG{l;Ul$b#&-iKB=C+ijt7?s2#-ZvAX(xzz-(QsEJ8XRdG6mGaAq zIU=)(mH!j5iM22^sWS8m&jhb%sg1&&ipvU!FB2nLLB}h!AwC68%xYP{A^HL#P1$9j zEc=kbVT2WV6M)&1s+DT<&?vwT(aXlbPTJL@H+^hcMQ(g_7glKbbq_Bh1Ye=x;7PR& z?$ag3CK`R2jAZLfau6i<0BpaG$hL7`aqgc4TiR&i6G^4(Qzksw>lg;R$m$k1i`UGkz5DU&s z^$!SQVZef}iiEs6EdqZ%hpu=Z_zH6x4VKR&b7tF-u!Gx!9$aN~*GCS0wpGpmK;2PQ z-V6!$d&?48@KJ4MX`+6D8RuXMxHgfLeqbxs&QgMoITsvV(B4Eh<{E4}vd>qS=neGTg(H z5xOa@U(2o#{7zF2+h(9GdE^x&Yl9w{mDha7Z4L`ZX9aM4vDhWpP1LCo?Sa{H6mhNB zRR>_iA11JT6r|VVYJz{MLI1xrUGWg5F>gRdY37 zzoRl;a?Zr8BK{I-wgvK<(vYRXX(U2oE-ZzG+s-btnz|E~MvCMS%s&=1@A9aagb2*W zlVaFAyNBTHhHFwt67wfREtGNevXWeoNr)_kY0aghf{}XMPz>Hu#=$6;jcZ>(HKnV? zX@Vf`kL#jeQX7+$rpwg`Mwi zhY*1eDeSBgUwn-<`Lu3krdf?gT?{6MHKb#r3^Km5qXUm$zVuvzFZHy)vp=0&7B=eJ z(7q4fX=7WLl_7+82PbJ=r8mc7{kr7x)RLOq(S%tusszXV)biwao$aL);v_Oybv>5+J1hoT>%NkXT|}fzUOylYTsJS%nqUA zuTXUxQ%gf*91Uy47g{Mo4l8t~0HWg&HHytmoeHd@3T#`qPZ(~NY!n=5fFgM?w<(Za zmN|r&8v3Y_TeaZXvbw4w?Z`V29fM7-1OlxC7+ML=J-y65F*sA*2p$N7uVIsM1v3Ak zBAvK8Bqrve?Vz88uK+3mnV;c1fNeERgba(i45420^`f!8eV3Q?ViyUyIEy2J|>|L>bq0?){$H@M^Oas%M;{cbd(dW17NOT>ucm3D}b&dB04> zPdG`H^a>@-W11nV?@FWzON@ESPE$Tsz5jln!Tk>~@$ z1cGf|lt8EdVqtopJds-iOUdC^joZOJ{1jv9{pc=(Nz5dwV- z%$}Z+NQZ-C+dU2frl{!t)GWDP8V&R>=8hT9m;! z6MR-%*kWbbqm8%*Bn&quqX7B_2~JXsLySnr2tFdmNE5a=w4ZRHh~blt@odkr${^6$t&C`yC~RCR-iGy?O_nnv=8Q zGJrikwC8=^0~dnttcY+&bS9DZl4mAOnXf=9idHq3Z9Lf z?4(E6LKp7#He1bO)vw8DROmKm2&>4YYuM@36u^DFIc-%fe1QieD3RDE`=u#X0s91^ zeWo>x`j^5Qol5Mc&e<;}d-JUOdZUApW%4iysM zxR}df<|^qp!H?ACxl@AZZDj$K=Macj(@DOr=B=5qF4ztHCzM-`h;|4cy_=s8W%76o zJa&-T>?!U1RE3(m$$qVjRbp12&*ky3Hdu5`IGC6+jZN7%7X*v4X-0+X?NGHYVu)=l z5e@VEj9Wm?#*)Tl??;Ig)}w{H%=7)^gOx?6qTQ-YSd)Og(fTd%huGY3+U^nMF5i-= zFr1KqJ>mt8R@`q{IqFAhF~?Y! zpTdu1hcH&^pcVKAnEMJi!L3dZ=$}t0fWHFa=A~|itwqhk16w@ZOF&)Xgi5(8IKpFc z*T9A0SBO4c6cP3iuRI!Dv)V3^rBoM)Z_-p2jpG-UlwF3bpyfJ&3&9#L(gVl@Xo6D| zNT#x63`8Q%rENsNg!y`a!@>8s#*{?w@Y|=js`JF>l$)d`%yg)xqBJAg(EQ>tqSqkT z*&l?FG;BO+Y;!3L%uIlwr?2-FV6E`X@P|~U8=c`rKb5aay4`}JC}vSULxYRH0^S}v zBF)4{Boe#z#>HH~AR|sY$m>Vw-L>HDcp*DdtJrKE!T&AShS9)H+=YVT7m-6q5lkOF zr3Mk1lA6ZgiFTE{3)6iZeN+6Dd`hWpeb~$n0!4Db=ns8_Vx%2jnup60?9|ffbZ|?9 zYv4Fg-wJLu1}a|Zb{o5(E=wBLVp>5}h8Lhl*B9|7Bit|C!Yx8472LEpIhbq+-=}R{fFdeA=q1cR{OWwy85i(a3@O@MTzBxq zchPd;?^k7UT>9=Dwt*1)@jTuyEnD`CwkGW9KVQ@{7~YCuXkU(cjdgzz*A64xT z0~Yj1YH?(BQg%37EN;ydx?+I}l7MLPz+u0uY8aDZYTX=|h1T$Vn=o`!xi>0oli8|< zjf}6PIGvB|6b)PQE51snaMwsXZj;767S*Xm97)CZ*~W*6jL$1oFf)h)2%k&91NhbPC7wpbwJnrx z-W!RiMgMWlv#XY2%OrMU1CJOn4hbyTM>L5Wwvuz;?8Oa*((bMP_E zD@YM%j)b`*QJTh6N}ZvuX@2&j>gS=Ot81{tFgfIwT0_AS-9XkIk}K=*E8RcbUYK?| z0eR^AQVglyH}6dndiwW^X$xme_w_rB_6Ur4&~{yPsM9vaX)#4GTEoxi-Mxg9*eqeU zr*uT&_jY;BLhcHJ!Xfvf&vi$qiIXlE+x!6q1K8I430v%|bMS3clu9Vy5v)?axgVB+N_j&RcWY$VlA~2A>lDZ?OE~O0S1&lR&3Q0~0|A0_=VgHV*spNYMbCw3eW8xMz3LpdFMW<0{@zGx?+z!Jre=Ft5mnSJM#LkkT5(QAga zdkET_(eH;yM^YZ|qP5}PEM(vSl9knd#Ar{E)MyEGmadn4--paK&=6Abu<=(yPI6t! z0k6Gl%9myRV(TNC^90mgbT!B{ zYBsn9Pu=v-bqPYI2}HyMJ|0pj-fju4SZ64i)^6_8u7|`@8u|ioRz#S&MWS${V5o<% zG0&3AUrljeDB~Eu;kgV-+#EF02T6#Y#Z^vYRC^ntNT$EOX{RE6owH2*7TzeURvau# zDFcm;3Yjn7-;M>a68GfXY*Lj3e6Z!(hr<=s9;C3(y7%2Xq)X*5BozyQP-HEc_kT5R zN)A>mFxG2j7hOt4lsoYg>8*;Gsa}fB+y>$G>K$o}8+ZDs(IL&Ov7-GqMhd#tmck|s zk}$6KvRuKA^$W*vC`iG=K(pOuQtPBS!*%btZ~--F$tS5-gUJnDx#+)z83h2hHw$%K zqaGs|3iBICN8X$D@1i|+nbJkL%~aOWEws2FtEfS>7v1bo`yIg+rUYe8cMJpM;YabN zE^cEtCQxR)%IS|B# z?sdX)xEDS7x|C+LtwB{JOhI7HnR=##{Sd?$RTOZ-S(_<#rwRNT?52>L=yj)cEq#5Q zfu941QJ_zEbmT{ksOjqWV&0SM+0X zt4L8vWK|p_U7zCxnZ+|bWF1$`vbWNM=yX}^bU%zgUrJd*Zk~q+H)5CTPf=^OAKF~W zo-4lTBlX-8C7Kq^Zt28c zzS+hmiu!wBm8WM43l~_13p}gZo~7_!fq-koYl#6ByVo8kZb|?A_1>QZG3NQ?PQlth zVdQedRCiIW($?Ld55t!d*`&m*1dI&~lMWHBboQ65bAac}^l?k`VhBC$m(2k}^)3o? z*TB5LwOZxiu?#9aIRWGmWjb+fK^@za6(9$Wb#m^wHba?q`@>lgl)#B&-F7UO^zg$a zVZT=cd_=Qo=#{OVbteAbs{+1MX=emH9@8?VT;Lcylex6w`>_*!M8Co6T%{WdC$X?j zk(*B8k}XXKAjCww#W~xK-Xnao_=$TK7#cQ5ih{jKVJU+%OuPwnit265_*B^RC47d_ zj*8e{ae^lox)2|FG5V3o+5=J~!Q!um; zy(+5$*kWX*h$~vmJM!q2d1NF(%i?shy`=ccfsl$DTE_Q9j8=qpTMN}b7vk>Xbeyh)g||C-{A;B#U{kS$gl26ja2LvM3@lYNfub~^w+el zGy^>r#IsLDFUw_F6+NWQL8^R$iPn3<08P#|BSIuwmU5l(s9rnMP-LcGE~oT8k-2G|k2rC-&g>BED&pAJBk68AYTG#Lg$dPKKQ%OzBdB z!XA)>mRwK(2mh3W@%}CSDDl%cyJhm>c5t~&`)`&38Kn8w(}=d(h*aj+B9$qLrcQ_* zR&NUfH9TQ9|55ZJz54fe<8Bo6ZU(~{!;9u$Bw5me&hLkbHKr>~ks&3@PGkH4mQ@O3 zU=*iv`Cn@{2{GWZIv2<)tT7TQf`P)vvTE;yMiQ~H+s`e2J1oITowi{U`h zAM-|utc3ON@V@ZWhtDk8o4P%egB&2?f&_mfRopOy1m~|W2X3n!Fe28yS?d-XDhpjB zaP**k9iVp?2@RX6J$QMoogTnlh97~>$W3S^-1)H253xXIS8iuDS_2i zLb{eNlKjJ`%o8zNYZUqgkw2rBi2?mralgj>`PX;N%EJlN_4XY5x&?VYlacOFGf|NM z`2tu8ta^FJ=s1JLHr}87W&(RxDGPYBAvMNL*F0NFn^%9Lf4}Gm(+`zODgxRjYtIZ=agO5Y~!|t&J|_Wau4LW??8Ao+&pu)**euT(eq5qGcWl2u}Lr5l~)td~a)zC_kSVe~6D$}1S zy=@>lQqDZh?wC!PruK5#?k*`pAC#2q;pnq~3vDP6a&sM@tiG&x*5*4oO$=VhL62#W zLK&H3pK+tjarPTw$_UeGODyn7F5mWU+bd0+bU2to&X`{U&h~?gJ_6!;qsK)^l$)nA zj`OD)sVd2~BT5aReA*CmYm#hcUqb=#w262X^8B$5lxta~} zO+buXnVHrG$m$UN;hG=)la`S{q2A!CfwLCl?B@hElk=)Hi{)&CuA2IX3U%%Df7g7b zKu9{^-vKj|V|pdc0x+FCh~qH)L*sju83~?BkKYQF@C8-sKL1CC*n7*i|D^kdx_aFb#5= zr1p~ddv_!xxT)+X=I9Pxq-O4(HJ-fOoPl*=I^DP=d(1H31wJqb9ljp6DgoL4@f8U4 zi#pC|K!nCvvdORnK*TK^FxxJhXm!R&bepu>M)rn3nXLf+77XAix!Q6cT8R4E2TvNp zCC9RChQB)-h9+Q}E6u`dfC8UiQB?XPhHSA|tWG0qy>GUAp7AYaq?Qwle!+x!9z_SY zlZ6p_s(eZfo|}lY=g&LqSNiAiELFXniK#yvOS964dYWE`K2+inxq?L=-<$PlLWV6o z6TwMdmFt>fWOKrzfV!F2n-)MG50krO2CXY%lLPncp*TM`ffK=d zli}l-gx9zhH^-ARO2ctWhMZP7h*nPRaXaK6F#jj>9|9Ktr zkYNvHV4S!Ht2I?y>Q1xIPV4q+rZZ8hyhQ?OmQL_=1vqja*0ZRKz$CG%2btD(89L#g%^ zXx``Lu5o%)s*&R#r;z4ogjzW zq9Wn^Mr*Dj)GfcyDgzx<7FHs#+0@yAtdJ8cV-oiPpeDt_XR$pgGSyg)pf-|}qO7Up zy!WCuwBPuH1RJRYzll>NG8Q7q%(4lSJih-t$C_<)#RqQNZP4r%37e3bRGDuH9DazS z(V^Zl&m9@&s-L?WBi!#}QxTuWfQm$2U|iGEW+n1Q+)|>qUP$omw=qKE%0jXvv<2@M z;~>?Rz=`-v6dyR@Zfo0T8NNsoa1qpSe9A?YJ>+DZmt&~7Kl>b!I!HbNtsXT9N5Dc? zQp?hIb^{Nm{>uHG0rV&w{uIWg+=jfuX_^RKRXOJb!WTzzLg-zQCL0T@XXJ0SZolyK!j9tJ;lFj%8rZ>yc1o9G^Th_2*u4E#^1vugkys>p}6B8h!BWyOn7E?`V2vo+yMv4tqU9AwQ^2gO+j;3(8 zXAY&Xm@z|j{KVuyY~AY-@0ZJ>6Q`OQLW2qMOmoB0R6DFsDaFfb1oMS+T=yaluaAAO z!sg1ctr<4_2yvPK?^l{o^g-+*{HOA#_zlJqBse}$o(W81EZnHTPf(|U<)n|XT8%{E zYc@l0E>6lXrVC#bnkNW8IyY3dKK@nsvKp0N=?H}t_~DAwy))AU%Y#X5&+7%eCsEb<2W$5-B<-1S4}9W-m#`zCuH9F*qk}U z2G`x8)xf)BCPp0`ATi9&*cB6lP)X3Cj+YWHI3BWWSbr5Ukb2^Q%%~pd+35egfyPY^ zf6lK`f3a(BaWVy8wI;7Me%Pzh<{sLyku1u5A+#W$u=$#dd)Ju%O3s4Snpu93VLwoG zx>zIgl9#_9%hL+(PnoO~mGdY0v`2~>^R(t_ftOSNh^h*ba?FG$jt;I7w;PEBm) zScBcDbQkuk0l+9tXfW#L0P-Jfm)Of^)ml>1piD%2-=M^lB} zbZT{f{Ce;j{-x{N$9H=D*yC{}$0X<7%7KA9=@;ZmDD{V;4Z41cErVsOg(=E3uW7}| z2NXm>h2w0NY4OK|x*H9V%}y&`JVIA?BXeuP9d-hgZfr(d*zcwOQ_tMNVCen2uhuYU z%h4zoPjR@L@&zQ#;MAi91!A-BUoU0~r?WaDy;(e0-Tg5LJP9`X3gWXT$1UiYOb0Jr z?q&!j3MjoxHYvd*AYj|IUw?XHiwgRuqnG9s!MbS1K1QKMq5o*2$McdXnkcSg%Xlsi zuFj4-HS^%KyzMmUFq_@XlJe2vhr$_5MY=?&v8(;$<-V&N7WQ2h{h=!6e)_73Jw+J7 zn8EI8<<+|8@Gtd6Ql+fd31d6?!v)zjds)P_Vxs=L=t*nzH%itl>N|xZXCyq;Lz!zO ztw5o{ZZzYI;+{o|^;I^Ib3}(>Nxks*K%-;>C+YZX-cGVUkt%DJEkSBtL*cD6GHVf) z>R3pp!i1j_Wz^G)ltHW!i!C+zY(=LM1{vwer;;X`+3^YzdrQ3D-3ya}pXXvJxG!X* zDT*c-+o20+P3vVD*~6QLHq~@cCkOQ0fs^M=7dgcj&MmAHukJ>xQJnjcE53@3KM4Sk zt0~gZt&+rslG55Ump%5eIf<8a)*FAL=s-3F@AL~ecEX*&C^w?{*)k0n5c?;2wG8K> zhtlg3DXSL0`@-m6KJn&XOZi(#4ShZ5j!6Mu-jWY@2iagzwH`4a@@3fNwtFQ1S=O?) zubP0?7t`$7_!0jFaqY`7h)rZCw~fFagNm4j>L%4ZDC5|`*(Sfb7J+dl+qZnV%QZF} zP;*R|Nc|?C|tX;mX>D(fX=X5pl^jqT6?Ql5Qk61--ak20i!!iXU- z3le&PQG2{>Cu&(6Cg1Hl-WBkJy9$^DC4=^D%BVa2K1E$ zHb=M-9SzVWza!GEVtYtF^9iZb`;mgMFzywHVFGw3i%y5_MjUuk?z*Hu(2EGSFaiZiHfjH(~S;_Tb zUy>ZHMcVD2TAV(>Jo9OSWtSwF47-!Umv@j#nu0FOeCX8$+=E@pA1jaEkmLFaWEY-h ztr|n?4aCY`^rjiiMhzPqlO=I3MF(qht2EIAR==$)n_A9gDv{?wPiWUrjmhS@^AN8_Len`HSZL5c) zkJ5qgt7cRF_Du?FJGtJw3BAP#7Y3k5c-PFU@zEe;cs6&U)E*umGea4t6i?7!D3g9YNe={|1`-GD z7rqSn)z#|M+p%;XS(^d~1}?o_7)1gd?CGf{wkwt_0*yE{{xs|u&n=MqrqI1E%G}Ij z5@2W@9@9+Otr|MV;H$VO80*GP@i^(p!X;?2&D&mwIm7N~MSO^SYJi(6NAXME z@h6%rb@|9`O@BEA+OOax{4(uw{Wa;;INdWw_*~82qay7H%Y2^8vcZSWqm;Hg+utPz zNs;~WR_?EjTo*Ji0_ri84LiewS?&3p?b7e_o;8Gn($ZAdgDtC-?E?cURPXfjJfNM= zc=~ed`ZWC8^FaH|v(#ILM5ci?%}q@PU+c2C+%tnfI~ zmL^0RrZ_5GmTe5|aY{?D*w@(is73V9K%TG`j~1=#cR*eCOc*We)hk2^wKy9X*22ae zX3ZRx+!27`^(-XSio9;HTIN?v1VCnw9Q|wV=79Hl!K1%Eav_nuN~XRF5?v$}TpSaI zWtg}XusvYA&c2TV`&pj|3UhNHm#bV?_#PHmF0W{)f~h}6)QCWCbFjDNH<>R>r;GH( z^G;-Z7=$u#V(4to4w3sv47wcEzp^qQC} zI?}3k+EM(A|<{;3Cj?bQd$V)v|0$$?)XS`2qhZT zH~JK`#JKV&%$zSYOdiFr#qPAcp+12vGF!GlUaI0}8)gzsSAb}DjhRgZk3Gt8b#ywu zo<*5P3lrTY93dHOybRh5)u};6i)M*FKymZZ3QE0t+{1ICacMJ+m?Z-oHp8R$ zv>Q`lQeo$iI*^3!m#ZAkh5~rsp(Fk_(s%k@&fGzK55>YlgF~N@=#Y`H-tuAA?m43{ zW6x;1wZGT80OiYQUv=il@}Ib%BObU1qqc-$jnvZElE8h^Z4a4rGBEv0(`ZE7jN4qP z^aw@IG(-fwZva+>j)r(R1tj(mjkzGo$R0+LDh&hg@k8Hm#wFOF7=~daB-`y9~ z&n`~>@_DD{4}Qv{zM`+Itl^sQx$7HL864Y(KQcmm%t1dp=rj^SM;vB?pVF`~na*s0 zRk@Vqt}CD$sW>KAo|jgDw&gJuiBi4T=^5h^hWM71h>5hYPpe+xnGGz~uy6FggGS!0 z--$jPRfOW$uTV7C4$)wue^y?9HblZ0SMYtSn`|Bx_uFCb_+y6KZ04UG$lQo}pW!2S z8&zBaDK-zCEGrSiniK6g4Rv&?LxCeISwrA$23Z`Ev#9213wHN)1H7PD-+b?Mhkl9i z_ITs969IpIZp88a{sxG%lezdm(~tku`#m!S4Q@~jI4j!7}%ManE&1SUpdJv z|B;;ik5u;0$p4<5{Qnf)#D7G$pRbGmtLXNx#B2QjPtlE)?Z1g`KXli>MK?AE`hPKO ztUpj0+t0XvY&I6ge_k*>K0EtAX5IhKgN2^$hgxHW`k8;2wtubvYrB8endtw)#Qy1F z{Bh9O85rVS@#HRZ5hW-<1`-wtVayGFM|FQ0V+W!r-F|e@w zJlp>U+Wx7*e@0OM>mdBsr{&*3+y985X8j)#)IXu-{{gt+Gcqv!pm+aopzY15hFlV zmAQfGRivD)@GTxmS9zmJC>% z#!iN&dd3d)=%|(WNce6-#Ax|WMA}L^JC^$X_%};K6MbJw6FpL5FPt4KPUFT2*i z^~lG34PkNhgMK1nQP&@Q@sHgA-&$riFMM;~6P#_I&CzJ#{(##neIuCLGXoQSXyI4g z?g=$4js0eyc8#}s@84XH(*z}cU%Lt4`>{aVD+3RBZ`F;BmHttXysfcWQJ;Ed$YS1@ zTt>)ZTHcUmfqrqHda3?V!Hw_VdOP0&A8gt^OBZ-9%rFxT4-?;={=o^Q!5Lw-9l`f} zeKN3>--wSnQH#=FTwIK#Luh_BU+aHH`Bc9S7gwg1);_;mp6OD*FC@uwX({r`B_8=q zZt#c*n?zlQ(3(tNB&WVHSnQd<@CN;g4`Hdl+Cy$mzjIo@%PBtm68|yUj!a-OacMSg z8c!^~RsLnPeM@qF!;_GkA0J8d_eRR~4~YL9%MbXm-hrdf?hcl>^AmH!^N$?k4`|S{ z@5pZ@8B$VPQc?MHDpyf$f#fn8fB$n56hI6(sZ#9*g;7m;S=RBOUaubw+Su^R{j4L6 z1#J7Tv*y{BfDb52Qkk4+Es1d@JrD*l@*l=I-vkg{_mtVP3bMmi-0qfu8kkCB9D5&=cR+@q#V6>*W8qKno>o@{Sc`8qbOE-DwiBK5v{G zmZX{p|DxiM}*yTQ`_ZNUZ{*M%QoRl*GHAV^$1`8MeZV9Wfj~YpqiS)!PeYVie#Ho+7 zAhnbuI;s#D7}y1nXWWEmO~5KGkq-mW!07F9MCw*G%!0|x*qvdKVVC;~s;utdNy-j3 z4>I_-u9?UxXCe9%$6t4h07#9!{sIh?pXmF=;EM#}!9G2eXa)P0xBa2wr97CXe4 zwL_n8HWn(%GEelI!vh4uv=%y$Y7qXUG%@|zY9}Wx6QE@RP<6F(FJ9Av0{tEF1(a+? z*#yzJ@WXZcrjo7|`_=LztSI#GG0@I)S(S3>tDq(a65!CcbpD*TQq-Vhr`iC%H;1hn zkeS!VcC!Zl3_8LbK|Ca?LxG)$cm!h`OiM@%Ehn`HIbvWtg5L!9*%z?Z908ejxEO6E zQ!<=%yNHh&jW{u}4cVeIzB@_zlAz&c=C0BIzC&mUb zx8c=fJUJ6oa~QNgYI4<$xg`0IAo&HnVii*)C7udIH6eLm3oxMeVQXUUO7Z+86OYc* z;a0Z3bLT{kgAh!88){zeI$^J(!ctJzNjPz)lOHrIxk(U`#3@GQew|d%$YmJZ zjv5-G{o!Y6AKHnFM_|PwqS{xB7Fk|p#JOo`OQfDH98f~+6W{b6xZ=%4pYBa+X~@4< z6R+AmZCmu-oF|1-KnU7s;329dS6<*kc$uT-Ce8LfB7208kvXzh^V=JQIHn}rmrQPt z>#Xfuzn=0YngGn{XXNl)E@jQ2NY`ZNvGK|(b1nCNV`FEDMql$SEV5bv6q|VuO=cXL z9Q_+H9eAGW-!onrLBz9wyEd5EBvIx1rB7hO@rIC>UDFDo1RAnbSPAMrbg)1SwrDLz z0m(ah?7xSf088xsBhDw9ZaTL=-wi(1EJqm0wzu#(tKAqVWg+vV zdS9=9_z=DfLIo^B8FRsrXHU}o^F{!L;oX~NW^m6vRA1h@UBMoZZcmZ`YQ_+)?grGi zBp@j$_MIscy;A7=U8eq(;%3M|(_pexGo#kGDvNPs@=U$GImb?`$w%v13%l+CzDAc) zLS^V(QCJCHrb6s}81A$-|FtmugyW@IU8Vz;11 zl3olxp}fM5vlxINP#yRBiKE57<`-->1w^|vwD8yGTBur_7fLiVaxduIx5u}nS^XvV zZ@0S#lwh12=F}|tN+=-K9r=NF8dJ`AWDIV6?I`{TT~}BFF$NmkdK<4^nSo^Os#)Bl z4C`DRFZ`2YjV`VENeeQc4^6*RO$fjdTIQASG5?<5XJX=f0KeUs*IXq$`Ir&Uqbf5D-AAu9PTK{G-D}KG-4R;p2m>=OiAe-{I>WaF#NR|5V!X?862xZ zPh`9E{ggI?Dvox=4>cDK)$YpcZL(pYICAe}jf+OIPFd_b-G1jId-G(pb5d5s>dUcW zAf{(TB1=5en@x-$VL%H8USY%>=U2%C;Wiy?(v+ToFYy8%1JrbGn=IJL__+|uHH7Uf z6>{CCL0OXuwtj>i;wfH|ib7nJCpX+5(UZ>pePYn}_7x-C7HfN23g7X0QnBn8aWeK* z*kq31B{ssi2E;1LU3+^@a|E5GgjMz1$+=0ykH(!*_*p{D)cmN5nKi3AQ~i*YEjlwl zFr$OQnC0!rLNZ&BI@1QqPeGz<-Opo}QTI^`#-d%b2*mel z7O4ExNu*;@i**aRbo~a|oe~+I#%Ozmv}zAB`A^P=nKwLc-PN$;vJMa)VxIoBmZ_G! zi6eiW);zT9--!osEIw1>#^k{|d=GF$;zGls>R|u`kG#7v_cebCtS79*OpU9{pg+-p z+H`bAO8d<3^D(mA(+%wMq4E|+X0P=Cd+Nhyg^k0(9~NM;vC7OP-3t`;ZpNT1%TX7~ zF2Vd?<69Mqg}vVh=uW7Ggea0h+aRs#q(nai+v}9{k2S2Ds|BaWWo!>^ zGu~4b4<=vQIOb8`MIOhfSsLZie7%G-7YZW8_#VXa{mx4xiB^ud0Z`}5)(HRHJI(AD zp60(?BKH&7`}tuiCRm#K`mQ_0J`~dJW^~8(*t9aERJ|T|m#`xDSMpoE1+Lq#zeBfb z6`vomDFl1jGxlq)U?c*0gySvttRqCo866=UQGEs`t9gbm&Xyj&(wdIE*3~ig4NO;n z`IU@di(t-e5`BvbxzQoTTH`lga$)+tENv+blgIrxt$m2ea#*~Xw4=2#Ur$LEa5#9P zFXbMbavtz(au=GS>L7z8ds1A&2Ej5+#>W-^-Eti#;@do$gxG;Do@X!)m43&LDs)*J z^eY@|JU6ZR^neB3!^4w44&Wl+1o;AYsUz30<_Rub}yl& zj_nlz#IFbzTRhR25d8*?BVUhF85Z~j>7$w<3pddo>;Ckr$sdwH$_yewaECL{XDF+8 z!hb2&+2KA8yR(TBITi1w-fxoK)bN&V_Y?;Dl@RAY-gKXqB6nIYU@{7?Dq>df<(FD; zqgda&D)xW|NQAY7(8%oNADHzd3MJN7L}4r_^YVR6M?wUuW8F|bO*xnsP?(y}-RWIY zXiA|1l=D?*KBLwlZoIXCfG~X>cy6?Ut;}h{$7@l#im^3sEN1dWuqD4n#$+tNuziH= zd^Sr(YS|y%Of-!^a}K5xuc`PI)3oAwaFqF|^gkvrwxg6UZD%x_!zE&#*n@ia7fZ&X z$0mnPg6R{B^H#=VyCr>*bh7ICoW{q+ypK)}oNYaWjO*~tHMuA12WGecPsDuOSDuRR zq^#{{8Kse&Og<`v5apnicJ@R}zq7Tme@}q13ap|gF~`^T3s%2B)&MQ)^LSTRjD;`6+NEY_>dXU@8!$k!Z0#nLgLw~Y^ML3r zuVFrrM>WPZ??4BDwOJ@pV5#-ADk(;Zzu*Mj#Vr~G94^gZha|{GYibv%9YbfT* zX9I|DC*P_yw@V#8ab_O=l37VC585^hFrknG1ymwa7y&vfv+L>v4p>Dk z5E1Af8?;_&uX)kGxx&xDjmjj3ALt2viGfe!Q9WYn0J*&wkB@+Jl9&PtiM>Iho}}SR z{xvTr0qi38o{LeL*zDwui0FKzt6@JNZ=J&5T+8S4=>RtIx1s)z>1=CYr^VglbUTbJ zQx9g^>NIkKT|P=^=HHf{gBapE95wGr0}&s4?@q`&MHE`oL^N%1O%M?MC3_fnGJc?} zwKZu^>nWO#?13yop<zIDW;9Meo=q9RP@N4w3TCvr}%7A$&C!V^BTGPP7!+P6ZI%&mndb@yw42g z(0pZ+jF0q(i_SQ%M=&M+%#(yWYJQW%5-q2kAg1n_iXvNs7N@EAgpm34@C|jq=-^40%0n$!>T*lv$=btJh zs+tp3ewEijtQlS%FcG3JlncV7QP|AYyWom#>~1^LMD!p7@l(2Cq^rt;)O1;Enu3s% ze)|Nzz%>}sAnvKdOcT~YbwEhUL5gu4tDl@&jKVj{-FjcRkTD!N8PYP7PUJ|SZKDzQ z&+o*pYj^iM6Gj1tG4AtN7yU%2uxv7cfa;Q7$A?`ukhG8yk;1yRA#EQ3SL%?*>(?Mh zn12i#&F+<^|<-)pkb=5GvcIA@O+X;;bHx&%fXL0=+otlnMp>@HggxaR#BIDMjtSf9SZZnW; zg10q3R*x~70+?3tXa<>X!=yFw=eZjaHr~FR>cNqazRaz-?YEScSRm5YX$2d$lM6aqV+_NuzaO#yS% z6t9X!fedPqz1+;WQ+I!#S7LFz+|nT*8HH$`^thW4*C?;=+%x{m(b5{Cq(6VS@GJ=+;bB#)<>;x8nrHDl8dZB(&lpI`g=6^g`Y8&vg~ z>Wp?hQ32@vv3QYH@T9Kn!k&p87xNwTR4!4A0iz%_oib@Hcj{8E{_B2`qDR z9cZCgNAT2$G|XJ#9pC1vcQA#JE_!T__~zjc2mji0;R#KtA=qmfVOcTET8wmhzF^PT z2-Y3OWl(68-2iS?@*xgojlZ?L1NL;m?p}jhH?%7;k(H2k0Wmh?^D*a| z5YO@Ab~oe;+|vL^jy*~rv`Kt1y4;w-|(wd8Pg^f7?E^k8u3V>0V#2%Z*1Y+({Lwfp;Y5i;{!lN>Jhe51aWF8WtgPU*^D=~I zH?6*Wl#lei{P{qn1RXtlgbO^6?=9ZSniw+Z0N+IdH^KbkH@udVO%Wy1+FimNNLMY} zv6Csw!ueL`o&m)l+Kc+lvEI865B(-_Zg#wR)K_5Q)%!?E=E`_DbufepgDpV?E!T`< zOZz*CGw`^vh*oYC@(&RofrFuwV-Uj9lVk2LkNpB7M>V@!9p9nXronV9 z4K&#U?K5cboY2^%3ly}vvU);8Ri@j3dYw?MZSH>LCex^$Z`f-kSVzF)p~rw(Jc2IS z;ECOGooIR1!kIR~q86eUf<7H`ps*@s;_bP#O4NzrGxG!Xzeht;!CLfpD*F)f{(z5W zlCx(%)nC-0{C;*G0XE~Eg#38IG3@i5GM*`;3g`y)Qf_E6^#I*W_OtUeH_`nAamTk4 zwN;kEvs70X(`S#0*$|HRR5Z7Q7jSz~h%I|DeMF95tCUWVS%L?WLG-Fx&E)1Ge^ae! zcshs0t+>~ik7~&CD2Z#Vx?H-mKzU1e)I!FI?k)4XOW|>(jqd*(;FIfkJ6aDlSqu+h z@pZ757psOU_ylOSe<)Ckqkzh<%%nQu$)SY)Igl++*y(-T?2;_S=%IFqVg)5RLvNi( z00M|c^y~a|P1|hBssiRazj-97i?^IE00~kQ^mLdHq|k=hiwP18N8^K}7qu78DC8+4uMqGm>5}NO zL~%&4?b;j=Ft$`T=))q#$e`Ug^Kl#JfJM9Se!W2%da9ViWaHfUD5k230+w$*96geX z8g%d8Y35TU!w>PGe(@rUwr(>iz?>Un5>iUVlalEu=(fq1PV;*y?)Hss%diqFyTDG;lWxmm!F#ug8BND{RU$aP)EkY zYtLEt;3nNRMOR%aC;%j-KPVr)AqQ38hwE#BP8d6}I|VKY`$8jA&6>Mcoc}zMQda0# zy$uCh+o~SI%lr@`f%Wyy0Nsydl-TWPK(o=IS>7%<{DE6O=qh#4j5Z7->Sp45ngaCheA0(}d^V_JIh(hQwL2DVeO<&~MCkX|^;qb`#^Io@=zoZ>w z_p3ko(0JC2PG5%dq%zeL-7M;S@?aI2*@OMR zn7ilT$iBBx^c~xFGI27oZQHhO8xva-+qNdQZ5tEY$;tQSug-aI-FyDKy=qtWdb)e> zU0u)K)zxdQ&ja!2hlG7Gv#P+|61#2vuHJM~Lr)`br7?c(I1aqlDhxo{H%0pCZ7Bd%)Ys4r=x~p_=hl9J4O^`4Am|M%`u=t~Y z_$8?FM`IvTCk_CB5H60in;XL8MV#EVP#%95+DV*tBV;fTLE`b=Lmd zdqr%f%G@^pb>T338o#0kQzLeR{@qq$8Sb!vEM$6rN8TBWF*hr;5Lrl=bg=qdTpYSx zAHi%7<%!K9p*}q0ksdYj|u);X=hhd$V1TQjNG%z%zzT zo!Mvv0M8t#yhvP(!K$S=ob0tX=i%?(VP?YVHZNkH#crF%#zht^QG5M<;JIH62zT4x z6e=MSNkT$%u`7B*1D7WAI``S|b=H+A+B4R5XeugOBQS)Na!9e(jdQsEilk92HY2o~ zBA}gZup^1|7RDP4V3VoB0n}Zrf=P9io!MSW^+n*Hl^Azyupz_QxCLVCzz>JCdG^)_ zys~CBQQThZ#wt-mTPh$7)cgY=uUY74IEFhO{B~kF3%WPdb$-(;>MkX&IlO0ip5XdH z$6tbzJVMlQ{P(Mz_yJvBQU_2*+=YUV^1q_S8N3*RAjc-a#r7Dp4+kaW&CDcBO^hc_0jLKSU9bX&z4$@h}DGwPnmgOQjH;+ALy z;f=J+xlrLXU+7`8+U7y@c8qDUAPr9m#?(Zlsaz6zPG1~c~ z>5mAO+XoyP%VP5^G{eg+74R5I?0#cIiZ|`^^36J%;EHW*qn?9En-CE#DOP9#$8j#B!Uv?efwP!WP!%}=_b_ClK zD2P!Yp(1>ukWSHM&KY0@270;XEfI1t@{xg`U8hT{td~eX*TTxnS9~B7YlA&yx{py3 zWM!=m1bD5vXn@$N8j}1BM}5y`?nYWqQi=$tP>IKTTrM(dIfxKdD{?R|4Xo+o_TY_~!wIB0{@Sls=lIR9UMU(3iSsmU5G2j*S{WLRe*yEm6FK zY7B#i$HUlzW&;k<8Y3C|ku}{b49N|*_L*M9@Ba5C$(CP2A!LLQc%OLm=}I z*X+&28T>E^hofwOt9M&AxBhK(vawgW@3{@Xy>S|e?(_5-SZ|@?#52Z@<=X0uqwanYhCQwxPwtYZ5RHhNx=e=@P?&*Pe!n*U)^$+j@=Q zp~Ke2uH>X-E!L?$lNvDHd-ko% zNS%J813YUMlO@6dzxAObuT&2nuNL z3cILnQW7|hM^}SL%TO-rP>fWb#XIn@d;lQ=^kb??*V1(~w8G+*mWf)%RVY=mHWq6E z0|k7h=A}4nGLS+%rdNkM)>SW*BQ%lWfFFU?RwN(FJ_?7X3Tk-5eFCKFR)Y&|b4Qsa<#38%9(`1jXaWvh(HMekj2yhni4M~%>z z(NmJR7|BApD$??zv~ocwB1MS6nJWF0T|Dg?dl~1NTUgxqfjz=LLDi3HzGIAAUdnTFDT-%je&dX^DIZn%w%C60UW7xPy-+ zzgA_fiqdh8t@38A^gi&7ElGN<8bfHsXMxb0-^vF{TECycMAOD^3+X4_e+V|{AKh^I z`G%j58&DAZf-(Gq6?49C4=H$nII!W@$=@tm3tIHEuVu?F85vYcBQIsNkQtCc7tOeX z4hdwcs5FA&-f%VLRsVZ$uRGXi%*Ws^t-<*oW*@2O{ah{(U6W?(`U>7VnNBzrYH0{0 zAf^Qo>G!p#OR{U|+z-_YBP~Sv-`a2U;EMQBIM+7d89o! zZvI2X*)CAj2d2H&dRGhlr3P{8HN}8DBlJe{@}xthsone@u&1)UBX&kv5g`pIT#ID3 zM1Dj7={xXD^9L%^Eupoaix2eI5kj=2toi8eQA{;zDL_qK1QXQL@!WmuW@(p^{;TS@ zM-Q`<%R2JOf=3k=O=h{U3lxt<3}Q}K@#LClvCx#kMaWj;%RFxa-P&s;E~6+DAVr>v z8y)l!?c?334ON81PWT3SlF`dMnCeZ^n`c}rwv7nkBYkwJZXi906bw`2>_?7lSLa)o z$1{s7yukTGmQ%}BImur%Zd>Nj+*7-}+0=bp3yP4kR?~yFG+(=b9@vQ9xnng_g)J{y ztR$c>t`4-}dSJqbkI5cl6fISOSHY(98EMwkOPoNkuJw3bG6E`5g_cdVtARc04%0wt zO>vpf1u?_?a~t6*5O}baEoH=ISM#cD-uypsi)B(H3Z zgc*>WDFquXy>rnFR^7wj!7`QKLg=o!CtIwC5il#y1cuqMyKcf#14@5S4FE4;3KpT^ zd>)3n`>6N;5< zoC$?lVQ-fWv?M`6hg@GTp<=qe|GS!wz=kE@~NUweSpptn;UfwU- zY3$vms;^|{y8%EemFpgIrdr@#OQ8~p_s(L3gyVWf!k(!_AJG?ztLF%HJmP9%a3&Tz ze2J6`Leh|ib{-UFOjtATMY~yK_aICJQaGGV6nAH1I+M~+8$jXO8P*ih!y{>z(9?7{TaIk;R2$g0BrwMnpj}F}zkdwr_SR@OX zBilI9@BaIV9G`^!=IUs-C%k=(tpXQ+RkU4Exg*3kzix+q;deU|cN$e6*HE}@mjD$i z^>pNoI1%)TC}d1?wR;1cdecXZi!!V(b8$^muc=&zGv1IX?zGZ(&4UtD1Mh9iz9mbh z!eRBq(;b_tEV1@@6~{dv*~1+sa3L@CfGW!|%Z}rEBBFPmSk<~Y-_g8reTmz}jRU>@ zf*i{I2z&DpoUt)?thAY^!Kk(!+DV4Xl=@O|Orq+dfTg@v6me$Wcq&~FJ!b=VA1X^r zK|dt~lV^)`7H~+u?O{s)(Yu5^o#)y`Bl+EI>EF3F(~P1#)Ci9xzVDZdLURwBt4&?c zD&o6es8~C^(P0_`;~dmK{qYl%)`9tF|FjSEwAWwJScyJUE~PuXrUqFS+D3XkemtO& z^r0!WSkj7Zf6Voh@t`M*!6!RtnCJVj#81yrNevQ?P~L$Z<l00{1P(Hxrz&mWA0gJKgyfFf2f+FXDmD1z1lO3|?k;KAz4;GZn9mnM!3|#u9 zQ_JcWCgKsjf(iqsv~N)*D54dCY^K9o7O|^RN8Le;moohE4u#PJ!+#v#lCPuzP1J#B z4B!oR6ac1;Tu`S;F?dvrw6`b;i4=$lhD5P-jJA<=21a=k8sMT_EGOML2D1|ZOcgoi zIt33qOkdztb4ziNLZnK%iXz~*;T)v44egd|xTml-8~&*!i^?1Fye{aa9lmj>aD;vA zeG6xClSZ=Yi1o@v$_Mm~0f#r;+uBuoSA!Un!g7xK-W`tJt>neNSmYN0DSiJTVFr<^>8s;oc|kH%z6dg^w5bkTI|n<;f%}PFWeaT^RHINpxn8% z_%F(h8?>uH8oZwn%3Ey5QE>Vs4MvKe@`dFv;5Dy1`8Dp9@@qCSw*{;{Xlsn&$3G}# z*ZxjQlh>ss=35pUwjYD<$@f_@9a-QT|6*hY@EHEw0Or}G zXa^27|0q3)v1>ccU@{I`wiGRTOZT2Wlu6(@n4nJGi^-Qu5P)bhogxYg4QIkbAXmp z7eXzkgD-_ot9r+GQW`>eH)6q;2X_}ED;n{B}8?>y-YWtUwYu^`-!Xc7>_{YTV>NZh&{2a5)CebmPzerr7AtpT|EXyv0RU=0- zwrRjt=$fm?wdNrQ24s)cT*Xqc8uTxE58Z1LWO~jzE!yrnfV|T}&kAnW$ddz|IkKLp z56N%7W#n+4|CV@*$e179vlN{F8vzej%C1W0Qral??m zJiZ&1%YyJ?&*_LWF;p)F%BmnkX>R+NHHZJPO1OhpEcTn$9Nah zCAD2n2Ca}9Ca8v(EbL)XW1)+gD^a60{V8ap|3?*J`eTo2_1Q-&stY**1SAg2;qSJ{ zFnS72gJ%cc*k=J@^AU*TG9UE*a(@4jC30Z%Y6DgCtYPL)zBC`IyMw0lMOdP8b^3Ln z8$?{I5MKY@lJQj;^RKt&bIJ$k@>+?D5uXM*IV6z?WPa(jfWLsjkel0q_Se#3g4K|e zlxN{?2v5xY^6>F6#TTc_`YtLm?&nz-+hcNxmizOq-^hL7^A4vosWrHlyVfq;0U`ZU zRU23syfk6c58$8+g6@JXGJ=NaM6avL$xdyEEB|DH{|7*(b{*=KjcZuc#Tr~6@C#Vo#Nys4dS z{P6D4ByVkyHa7Ws*q|DbCdx|yANDG6T8D*g-P2k(XE?Pd#Z1i*8VECSb}(Q0FC%0CNcJ!KiB}iaZ6()E~c>EW%=* zjqN^V%8j-Ag8R$LF(B=ymt;nL6*AP`mrOPJp%A?79839~Rn0MZ(Nu+{`g~-TVd4#h zgW<{ku5#z2VfqF~o;TL0{TXMZMLW@$G_N?o*Hb3YK~A|@#^>C}x8*A}LaC!IaN*%D z$8kU}qB>IQ$&j`(!)%~9Ofhxm>I5?toI;C|RxO)StVhZU%<+9bwjARxdnt1xSSN-@ znak9D~`Lk7mw`1%XKaR^eFR7KayIL4FPJcvl#EGGaQhcu{am0@_YrQ{d zNcLP0NG&X6hrW{ux#)u$U?^s}+sk<|nGUb+HJDg5sM`A41oE!41t^F6^um+ zVad<)4IH!g$X~QXf8l?U+dMn4&oQ(xH!R~zHIlvIbp5H<$M6k=FNeE7BY9m69@n;$pM@Q{;X^W}@ z?igR{HN9bc-QPU-WrC-ntfG!B;Nn5FHypVygNF8`BjV+{*f>#y8S4A6%vs0qN8jaO zhml*5#!V5uQ-ei${i{hW!&HC;1h$$abvb&XE6ta4c5Owbw(17p{<1d?iHXBU%M)3oyQ2?1Zfz;EA!Lo<*wrs`GbQQ(g+EP)2VI1 z-hp?&uUhAuePFOuUfHj`lL8JI&r<4DXXASCU^S8v{fWzYC6Vk5&XC8f&#^zb^&|+;LfwS!9VrI%6h=BwF=K(AEi=a5hEHYq?3&#nphK?r`NBPy=Q$MQH*!x zD(|IqWet8XV1Q>2DL)n9@Zud*mnnH2Q#}rCaIHdoEz}KNHiYRv}@XV*uea1nXaN>XsxEl1BQyO!&$=dc|{m<*Wo!%}7r-BVwb&HWtM= zXS^shMx>3AKy0&^*iBEthbX{j;wo(+aHscKa+w>!bZUTZzm}I7iBkyn&>PM{pp~l~ zt^;Wf=8DgdUow<`d*?o>DM_5PpT6Io6>I+7*#DQ=Y;Wm73X_n!xP1_qIpekq%lT_`aTw zo(iU4>B)~v!p6(7>|+cx9yl1wPIwNzo&+1IyTAv_#PxThtXs-riWAYJA zvmHn=JC5M=evEgU3d7?AC~D*02H+K75SA7Nj~)`1rAAj8L;gW)J<(~91&FB8Ta(5==!=ZPrHI(xE+|#$nfYq^X64=*xw#`}z=TlM6h@?6UMg&Ea zv%9z0v8PZr5~GO1fyYLfpj;n>&ws+mbBd7^GM*0q(C3TDw(XOZ4wI1svTnCBy6n=W zrFQh^LsX40Yd)E=CTojDh5gBEFdxbCs!Dc{0*_1-h`E z!;%h@c6Z7HC8>R*&};N(fiQ-7_l+`4tH8ouJRQ7FR^*TD{-gd6`EZm0+)c=0VUaM_ zxV90ecO0AP%^9tp{q~*jWR_`XvEaq_&K<9u>h)YO8ha?8C83`n?f_iNDsTPdNb%zj z>8>e5Q{N9VTKlf+hXrxL^B4oBc#2?Q?*vv#QY}%dva@CK;L!&;Q_=)iihLFF=$RsX zkIYm{Gv-s~!!($O6RJrzdW!SmQ&cd9Bup7PrM}|MeJ~?^XAq19m&(hCN#R6;Xrx+; zZ(&%(FZ8(yavp}r1&YImN74S1ld>&YRl(pSM>m3TDHP32x{$)N&FLowiv#NNYA2f9 zTILS%!E?q64zz;P!M$uSlz)8LH? zv75{#8BHN~gzz7zn}}PNUKoKy*$HSK5OU5!%(E9#eGNynQC*M*t8!^vmMZ|T zHtZJZ^ODR)E^2!~WbVlw1oWzy;67GNPqQC>P82%&$QXH&IK1PviNzr0?e7=7M1vU~ z{P0^$2s3$f9aGj8XN&G298CCqm$xU2jz^nzDFvD;%NM*0yksGmyX(JX8OeguW^89& z!p{2^j7OJ?EkznHOH`dnYC;EY;I?_*FY49Q{N94 zv#}0mal67$mv-;wV+}6<5c7U0d9zVgGJ?l_5P^;`rzVs@eR{}}%tF3X!k=8AUZpAE zCbwed2KWmk&tb&o#b0gF>Ex_sT4?}5C9aS%=!i{}%iMA5r)bAH9=?DAvBd3RAZ5`` zcZ0TUkVPVew89fVQ0eQxGa;Ob<0LQ_q=xdKciHXJOPgM}8m$67Z(`iwi6A51x_~a+ zXm45q^YC}EeLa6fB%9QnW1$ePvlO~D>3~mPPg6#j# zcoHNa=RPy%mQs46uuvESDgYY9i3)8bRfj^*7MBC_#*I(JE8L(c(S?!^ks9Q~k6=|) z%V91kxPIMRO7bIINcx^vF^NF(kX{19agTj1;#aI9(%G2`+5=n++}2Nn^yeRo{TzIP zr0~ej(UmjOK;CXgPaCZVVu604Pt~N7yQx*i8V?UOL*A~@PPr|Pq&v(KV@z$UBNPTw zQVz(B7o2b<6#1=Er#LIWaEj!#@l(sO2~xbl<0wY%^j*0sIdqK)M=kFrKjqA11q-E%V z-hQ87cby*I5&egAIugVTdz?P=q;n0qKZ{9S;QNfh%~~e;m7Y4I^9@#hdg;8$^Hhjx zEJl;Xi5i_tr;n+!^cmsKUydvlWJU4m7CVE}DLM|1pM@Rw%QJ)Hz|AJ_><9lF1 z;z?CRp_>|Ott2{w$op`l%}zxHjqf?n>kL`>6FN1>d~F?9-*(l&i6r>iZ)h0)iqW+u zSKONUf$#LkSnJns(|#T+d)>vUNYs{!u|Cx2K`7BPw$qac_&=)sE75V@+Wp&qW{&m1 z=dCF-^EQ1~Ha7?x@mJirkWq3W`}GHklZZ@A!@c3Ogj`r{+IQ~sEv$O)k86UCD1OLH zWs3>h({sRp8})Df#T2E9zY$%jM!UqU3paKd7aFX{-TFLq>6i<4E1SF;WY50UH&uJH zIIHs=QuI}TWD9hz%tTBB4og%HS1(IAqh8Hi%5|!7w%Lk}9dCKu*3onI4Z-D*4X@#B zyAl9vRuTl=*??%XRhPA%7)o7M*DZ}>$}1kd1d}cwTl%zJ@BP_w)d3DAEZU{B#jWk> z+cw{W5O%Gi`KNpns#olB74Ex?_UUV~Tbe0Q9%Z(vW2d92djha^WWRpOnJanypXHL; ze7d2WZ%nA-a%jv*LhQxrgcKPQ^I1xwu+qB@>Ggbc;B?zs*@Y>GJ}q_&2r-o?iajJU z*npIto2f>Ssmlzfogz$CfiC|Jvw+5N^~50p6Zdj?c==>e;mT(o*ia)k+#{OA%ShrV zT|$^c-kK>bd~FI-FrD3gGGatZZ+P*hlMA7bHthir-?3mihS=ohdixmL9jyQ^?JP6R z^40A+wMPT(zq#k^HX$EKf1exd9ONwd8$=S8aB@aR$PJE=!WJw8-Y*xeUVr2uMjxFW(9-aczY4&<4B z(|O)jQl^`If_^T#&RVgKbZfD7vU)P5zI-A13Wpro4S;Ma^#E659vOQyHnsD_p)az2 zuO=XkARP&DU(|>2<>yo_MfyBKtF)f2-?68Z$?yf!xw2x(%1y{t=N2N)>Y_v|z1lpa9 z>3#)t_QC&lVNLP_6$#X<&tzCsGl0j`%<8Huq{C1#Cim4`-L)q)2`myyXsPz?^ zBCHCv%T93uh_~;ri_j=N8%GZ0Hn7OO?au`1HkcwenYnlXNe6dIUR&;H^zLSqu-k_8 z^W8s7_SlD!UWAHEHHQeDu^VA@N^arG+!`KWXWM=0b5*Vqdcc$iWm3x{k5c`cbAuC?EcBkupx?)8I>@$dGIuUuxBrvz*T$O(uDHT$)H-Z3f zCQq)mto|O4EsqLp^t%jy4R28TLC5QQNg@5wYablg2bh!)#4@pooF>dvJRp%6;Rr#Q z?}XWUu}1LRg%cXi#`bx!tVg45{rkL0bThy$%yx5D43k+uLsM@)digVv4h5$GH&jB? zp}Q=>61TUeN={|8l99%QA*f^;SaMaS;Qm|Na(xrxzW>ign3 z*mBS*3;p^~-KP~nxP810#Qe=tWLZa(X|eP0IiVtmih7y5TTpY-6<2cb?jbBl3Pr%l z3S&{i5^?;$NCNA|-PPwt5YepQGo^&mHa4Ny^M75Q#>Z!X!oPDc&ikRj#vr<87Ifxn zwVM{NLTz@F?s`ZwP9&;@|3>bg#GO%v7N}7I>ujY@AUV83x8VLc$nkfWlU`psi$S*v zJ)7jQ8LteN>PPEDp+5&q6Mx5z#U;HjIiM%l9QmQz1#AR%kY@*Z0R{${r3w>iCeXLZ zxUZUVq!XL@h+&bb*39^JRXD8ON=rgW?G$qri81gv#;W(H_y35OJdYw`!QafH3 zR1b_wzg}FsL|xK&q}IWCo~2EL z<*LjEn=no%Lo$rWU{UKmTd4E*7u)cSUw0I_A<<$~q2Ev@@Aj-w@G_9C!hs0yj&WxT zg4t!BviQLW!sHX?S3qnrgb(Us{;c8#rtQ(hQh1kwAs(iHwi!++u(kGY5W{RS7rMr^z$}5kGdt%^^dC-cRnK`Qld*~4C zy;~BH#pSMBkBnO-vl9>OQz^@CMpYS&?YO-fG~jaF=G3q*pHy89l!+wx(jKsT(w&RQ zXg8J6!0pmgQ;GlAQ@tn{qIA?(n7H{yVt8clYWgKUZ*CyZ`= z3_2O_2^RjtNsCJ1VMhtWQr>u&CJjz|hQjJ`uTPj`dNA@yAMH`l>heiu3i}}ejnW&r z2EV*aYY&AJ4A@v5qnGdpI@&d0D zOWijGn$mDi{x2e%_?gaBp$9|toO`m;GuzrywKb`)p3RU_?cJB7wbACNT=jD9`rRe74=}hfks*FUyL2OKoC-oKjK8~JWOh5bgH(yqka?S_o zJnb{w2@UL$W$DJ6d;E+wRMg7^GoRkXAPDlKB~hvz3$is>NW?5V?-Gq%H_*4z10Cl6 zmT4YGG>MHHhE8ue!Hq6iBBxI+-bvH5k%_l7Pni)cW-QQ$D4{?FmoDki9$qZVDRYr= zGUzq9`05!i>Mp`3<9`>YniU^YG!EQCl}9wWeP=yFXse?L5M{nstlPy{wW+R!u+N3- zAH^Ly#q#YNO!qaD3+W+20BzfPrm3u+MpKEyMOjiMZ{8~<$fkTE%GRe}9gq;A*oC<8 zE(GnFPhO&qeLEY$WY(pq?ERtRoh~^`jfrjTQ5u+w7qbF822tRg_VFPXg1mmDxs0&_ zn%pYVrzJc}_&J*_t9#osEnA7XVLSF1;)Ow}1N0-*3+Y#|Kt)DSY5EJuU6C<7=h@$m- z7wLvsVhzKeheN|%td|tBk70DrV)C|j{mQrz^I}v%IdMmlb0Q-p>Iiu!WW;l!)yLuM zGY!+63mR8Lwa;t_I*jp!nn-`q)woh47q(Z&n4SpxZv^M?tMGPaH>T}^*ZEsYIDc%7^h+9Z`EZ1HL+`a?APJbw(a1mah z3&{)&Z>hT&Hp*&^SJ5Xh1*;^AW)*8w102~epM=?k6mf#4j2tU8fd>VHg}ccDJBy2FNj|4f+dAdJN{!YhdLQZT?(RObkCCOALM-AG>)W{KwG zHVln>0x5{<(n6drFOsB;R<}E!-gLsAx%k`WJqW!E zyCUh@A>`!eT-#n!rouKG;I#WE!F^KKtK1v^+VI776JP%DfvQ|f-uj8=eTE9XB}C(> ztkvZaUOwq~VrLp#-+Cyaw3tx4Z6W(!aCNk6{HN%X)`3bJ#+ZhW6N`PkfPCD=4Tjvs z%5k@9fq{Pffr>hT`*i1GbDi`rTkJm%+N2nmPsG-=ufv^$UJiFETU#R$#mRNI+-*#* zR@dDTBaE$9%{XOc0utk*VwiRx5(`UpZ}zV}O?n0Dk=avX6A!u${kl5>6n~O7$2QN0 zClsP$S&)Q+Xdc~1S-=;HAKHwM50ulwr|dLfBCA%;35ey$@~EfV%V;zECcei*#JNE$ zX&vr2x3?Hm;1HB}+M;jUYwIcyr`$8lcIlo&|2BymJRiNuv7u5S@**ULLub1bcuEpO zCp@{Tx%h%r*o1s^OTIguAv8hlOKs#V;NjD^Bqk0#dj}C^hx&JNE|d~Os*M~fU+7Pz zm^S|v+5p?}<;)t(w!w8&MlPCh(hB=m8 z=4xqGOx24ewmn5XK@1NMuk$DYxJGL+MglMrSTuh^u720W@EeAK$o>k$6|y1LG=3$q z1;65p~uSL5`aTlr|+V#qeDpjrIpDV^w5G=Xo4eA(Eiw`Bn zZFGtiq$>kJjDJ|A67fhf?1jy#U)(p*FnEO>xm;_LW8UdggIoD7=Nj2Np(0YV`D@Fi z$hlp*vr-Wdm4tU*eO14k!nNbF<|z50MR75o3zYQBQD0TZB7I!9A=yu-Qz9j?{1m09 zUyOPU%8PL$@2{VHC~)nU5Y;4j;EBwTDK|UhKP9-3&mZcF@K#&%qa5OeGqeNY&5{@z zhn9JrQYBH+vAcZJWike{FO1rcn11>Td3E%#snAG>Mt{GZF1)s=z_D5Rbv<|n@Q#*W zrHm3me;M$E0j1fVQkS8x21*fipZv8=VFivHL-SU+KDmLTMd-JrQ`lo``_bk}8p~yt zZ^pA--2{f#BR2S3(7VELT3uLYNW;&ks(QQR#$mHHY^c>e$b`o-XL_Wc+%{V)f58KU zr87lG)x4-$-ecJvZB~pLsTv_hFHD)ctbO}3hRRz7mLwU z)NM%6@lWTH%A+!k;v);0pkNA8ojBkP3Uv;X2Q~zEa+{FkNJNjjGiU!TLkTt?E4X^d ziRAlWOa{9~GeTGA_AN+G&e7ZEv2@G>MQ&J=Wa@CE{V zDGL-E)BZyEIJ_8fzp^^Z;dvKd-B7}tf~^N47vp)?#gm)5aN9e&F(Ow1Vp;Q8q}@jGUXPchR<{e+ZN60D zQDlBmW`kwM<+(3HSidw+!eb>qRXh0VZ?plm403xdrS&wnY!?Uj&fu1MP1$pxYc;(p z-AYwI)|cNLgq5*UY2RKU?hM-t?bR?ddkXZIcoJu`2)M@5y+RUMd=#8H`Rg+p%Dejo zq@uN39{S{iZ(t3gPdN7o!|K$8V4|kA8PZ)zXvSVfCO zr(^IQ<0AzrhUZUKIK|76EnrvOrODnT_es4t@xv)t{8qD4u}P3}B8XzSk)RgWl=RH? zdNcn`o_!1o364+UBGj;VFTY=eb9bVaDDZ4xhTglhaLwy{Mh7IiG0_-D?NEdf4qHFb zqUvG9_5y%0>mS4{HgCjqqozsfx4Sxj{Km-V=j5RF6x)b*PS2$|oQKGxT_UoPq08a) z}RUxXs&IwvA3kz|ayD)XQi}?^!tY*`oEbt3yz_=FQHQPIH#{!WpThY4*xU1n~ zcW2H}=e8XI9Oc0X^|tj{5H82dxnx1_n9x*yKb&cVXIz5O20(6lSHCN5p26PIA}Ip1 zRZb2hQb`+QFPAis+_b%A?4~Q_gck2n?9JP(d(lgc{UT*>`XVX*NyCP2I%8iackR!GyA_7vP|skgx}k@Bg@A2 z?}F?<8Y#=SditN+|76Ji>wEvV*Z)nV{oi8Wd;EWD{~YDt8}qk%`tL{mj|ep5_ZhJ> zG5trBh2>nbN@@(W&Ef2 z-^wm4E9-wMyWjRLo%+AyyU@%`-<^L~cA37t-Ty{*8NQw2|3-G%{<(Di71{l_z<*v# z|B39f{gWn&-)U}QYvyduNXYh2GW}PdiIDA|XFY$8&hUS=;TDby113^P~dWk>-A;`_`-9mT1N1rmCE;*ijneHavvspe0 z-l|G0m#aM=mAtBSH=UKS*Q^s%W65CKHB;SxvwOF}Nlkv+QTh!9|fvQSBCWFJH zfjpQdCsz>aF~BFRN8rp?uXPVSfR3D;yqk~>a1IOb(p+4bl4Z#hv(eA zs@SgdQtNz+x8$Ae-6gPw5MWm9?Q0)hssL*_u{%9)xkSsC*VDiDJ_!c|wAb?o@8rw% z@={_I+r&eI;}C$hR{xHf9Qc>fJ+Nm$j_EA)`AcXR5bn9W*h(T5KCCcH{xn{qy3;gK87!{I=xFPpF>t+~QV%R@JZJ(`(}A zrgpYu;0MFJ9K=1{TPzm>>?5dF{`t1O>~KwVUcRTaX#}u2YYQ9Y2TDWF zDvU#qvQJeKGbK>%xRDbQ$d<;KI2=?=TXp191A~X`Tf@6zl11$3&fOnw8(_Sm)Y=Se ziCd8-oZ8hkg^h)xBnOZf?m-3LL-kw|k^}R+B498)OxbWhz>)~+)DtmQvKy}Ki`3RE zM1;;b`TihG`GVLQ`vHO(JI;Hz#huTame?%~ zbSbj9vC2SM?<-{pD#foeU8X*Qdgw^xQ^Vx0!xt`Kb~Jg(OX2~Fa{{ z7jVGvstal?qbO=6Kt2mAZpidwcBcTx63na-<^Y#dT`u<3n^J8R)_$T9oL zjz3(}4Cy43pINrj60aJ;xFc?}GUm~I6v%pYAK3yu&O<#9+hwji?wHG19}8APTrGn< ziv;`HLFuYomuRJ=ilm|3@E&Yq#(91ey&9^zJPSE(dCx} zr1`rXWuJ;QjbU`c*nEU!KB4*g_g)rXSG;p!scow0MmOLr6CX#j1RGPQ1IEt7W^M0e{7PdF)Gz+(4^1o5q| z1~5WrI-;&fsZZpdDLGQFcQ=*PS`<}6T_F7HEOy<&J1U zUsa?uBjUa4FjVSf)`&}{L z=_jl#GI6^?r$QS$M@fTQlRgb*F$ccpjmBt*CR{ti%HeQVk}A7Hqqu_Nk=P4@QB2H& zqfb2%MoRz8!LHJ>*dH=~;Kf*6*qE@Se42-)R`bRW{{)maHL0ka?)elR?U}txcnM>g z2Ca$@5p-oixm*4(_UAS~HYc`i+nCt4Ik9cqwr$(S&UHQe#$Web*byam$*Q)O7>i>8Bf-V7whbH)SdKMt8?O2q%-HM>^Jg9~(J2lQ*7&J^+ z2wE7j(Z4G@-yJq?!MVpsM6b3v?pQ*Jgh&wur7Jz=hf5Uv8X zA^KFpEATnHqN9U{PystN{~)_Yq_*%jqEQx|K*GSm?pBF%=lJK9rfsOySVYpMo}`&B zRqye6CmEJIlKGoVEya3#L8`F+5I-G_Tb-$``3=0HB(-KMHvOuD2dKdO(v7jmqG|qE zy)1onxzxl5ZGi_XBZW-n#Gc6K4@&VoT}H*K_i396PoTnqt9m6-`R$vhvv0^oZY??y zISglQvQ5H+qg0xRDz8`3aI1Nm6>b+c4)nU2L{ zkWFnWP&O2-S5^_c)L}|U;`qN0m$)DQ(i*L2)>KF)yl~ebc#P$*O~mu?wYh9pxk@Zs zwit_>2w=koVE-PJxoydM#1o{iq_!xyAy(G>F_R^hE$K3m z&01%ARzu+u0apB0o`IROV;{1t##C^*sHH19QQ?L zxnSULQR4wglf;(S zUZ*>-Oo1!SCIDZ(frf2(BQwiyFW{qUxu>Xdu1Y+xO+|SAM^hTg4_;SpDxKAcR`K03 zy4a&YpU%X#Q~&NAq&Ekgpd9@cG!CMi^#pm=6P*H}wWYtW%CL?lqxb)YUMJMO^Z z08%2lNZ&cOJ*111%BCxbY>*P%Mk)5`M;X}!wQxzH?gLK-F00;+Ei%N`jZdUBZ)m(+ z1t+qM;L7R@>abE|bR5BvBdipdGP>{XwAF$phNa{J&FH(=Oa{ghRF>7q9+WHyM-)Q7 z&0^NlSVU8nihkQ+lpxCE`W|JGG|f{q2KHL$+bqjUjgAF8NSr-6^-!;U45lIT{?JVN zR`sQ@eid+M$PMgVC$+HokUmHW3T<}HM0#yy4?e%-`3q3bL2|i@ zCPu*hW!n%oh)QoYH=Yf#<*%d&S&BW(b`G<-Gm`P4YG@gOa6~%#RJk6G6Qbp_xvWS+ zB1?`FN=Rvjo!{`stYvz7K|rW5`I4`^Qzj?R%mRs-z^i`O9rBKZ+3_YmV6%3!eTrSF z3(N`R=FS$P)IE{)4Fj*P*XK=h;VSeZ*dY#Ka2_uSEr0hpROeQm1KX(&&MJ&@8Jy}3 z|J5|IuQJ`;;aBKDtAy!YcjV(6-*eiity9GhR*11S(REV~!O&ANhN;)dE7^!9{VvJC z!`-!rBJ|DaVG5}^4gzlVKavNbsyTF?=7eOSipR6QpTEnk87j4HKm4$$aE zeGW^|dceY0>~3*vFPp2IUW{O^A_wC6mWgbT*6HLILb@A%_AEGv3~JF( zXs;ZnJYV81UVu`9pOWntqI7hzXRj0rZv47B3AJ@u;-n=q%WBB~Mhm#1rHQUlS zX<`C2HA@7~b10NrI6Tdt+17N6fSzYE4m;KJ=q`|tIY_jaLEE!d4wMMeUzVeXuX%SA zsieADY5bW0Hu`>DUrpWt{Sum3BAqox^Jc9RseLToxZ7T&P3z?CCLT9~0E!n?O`%&3 zTttZ1;O;y;d57=&K}?IJ*Vj!t+OL4$7m{ExyIXxdz|Yne5+q0dQvd_6j4uvog zHLR)8%~hGA1&S^Ls=j^xau-}4Y=r3cEjat5M9@hyT{mR236G1hVV3pcbg823MBzK) z@s+IJu9^09O>M;=kpOO2`$j9*V-1yk5TU%FpP=^_p7P6(rS7O?Ze z`7fTlOIjg!aMoEhWVM5~aekMdbb!up>ppcO798dKjIO3M2NZM>s zi`oJ8Cogw*7_QI$NVRQ66r3V5=wz^Hgj@E)BXh4$d=%MKc_!pfh?Z;DdKALnYR$i? zN!q>$u5ds_kElt9GzxBY^Z-v=vWE2eC7oG6DQXDZAL7E|GAhWE{;8Y|Oop zSyt{E;1F41GA&wD)QVjj*MgIU<+ZvsXH7rxOi0u`bYZ&J$`8cz08>Csc!zH58sk^| zBD%E*18;MKH?BU7=EB+2%?=PO)_D@a5v;S$isC$@g(Jx^G3|8Gf$VnJ5%&K8VlfVA!r7?K^Co^x7>T zQ*IH|A|J-`V@Id#z`}Q(OS61?%@l9ari*oGn;=|WGs?6TA)~I?cgTvh`Qbw$R?IP> zmz%2{sxuc@#9V?o@3A@}a^wo+zp+rX7aD6l(tuW~pdlnfL;`*5aPj7$F+V?t@q4bF zz7H%lDZd8iSu*ePj6_vq3%<^ZBw+e<->mjhlK(Dp#;cJr&*cI0^f*%qlHstnhJ=vi z25P!~`&Vnx&#YyFhg}I!AcZqx1n@nAWMh-e>J%O(ANe7Gk-|%jpd2o@aIzf}ifTyp z&r{d=<_NwpEAp+CwSjs!ncdmn8Q=FO!3&r69{rr*SVd~6O?bq+dq%Ii{?c$9{uGX6 zCYD@SCfW+;-2&KtvP0m}iR3~<(jK6tCS)yDfk>zk&gx{@8$}wsiqlHlYtcE1mW1JY zd#mNjz`Oxv;0MTv;c_*iEX&w?KK4B0gt88ZBJ46@;>^qaCWntWf#QCB&2%3THByty z^d43WazeSbJ}*j%>n7I>h>$nqcAKSWH68BVcqo;$gi+Z$c>{@vf7g15Qw@-}>?Z$7y%ELL7_XgJD6>3230=s?+h!LJ<2Z_idVvsE@ zG3oVuS)AhzW2}@=Q+e9=AM0D4EbEfm9Hp%o>gr`{@Y3A$RQpEyh$-{8A$g+oS* z#r|`aaZ~I@CSzVxdUsQoXPgs$lc3VGfOYP4F{S?)k+;`ADttIF^?SkE!x5zNuJ#84 z4Wy;(6W03bndabtY|`-}qiSoi5FH0 zy{tX#_$zG%#P*KRW8ZzU%k_jTa^^)%I^&Ocv1_@D=RrTSU~B8HbA^_UjEzRiA0X>( zKjax$A{)lZ$5928T`}7`^tv)A8KTxc;%-|L-wwzWR)mQeYo=S7xPPyt;t3FvEx}Aj zzj6FveOoNujX)q0J5tm)<3l7{$4oyIh*2FUqReouyNAT$TV-arAwVV@isazt!mWBBZKc?bS?~m35UUYL>F8iq{kHu+Z2$9{_v?4S z0BbVUKNnJzUST3iGzmT{z#BP+4{yQ{eECF<758_lHC+%%#}D4l2F-jvdj?|;Mt;*qS=40E4udbOfea2?Y>gT_Rs3KWOMst&UWjq z;S$RkVX#kd-3T0@#TGG5lVAv-&h}iSBjZmCmuV(g^P|7FUPJW2?WFxcXq<1++rCa} zaUKPJJApdg-0HH9U*Jek9^ar?t9jD^^RY~@V(|UtcJ>e??F^2&R&?MNf=U!ul(hnx zsN3Q9d^>uceitn91A>m+_8C5WAw~LS6fPUv=49OYA@#P&V$eVtA8LF*P{Wu4<&J)+ zJrb!C{5?-C%I2g?-p1S)hmq%y1Fu%sKuT^>x`_wXBy{@~D+f1cX%0TL?AK3h4YXhB z+`rSBSC#AtwwDx#&WJh>XIJK!DHf?K#0;thF}RC)NG6;0r?Z~q0tp^uPG_r_K$H?m zOjhHrP)eGU{j@~Uh#<9sNQQ?bgDD;kgaJL*HXnb&cD6483As)aHfUgHgOM)_l4A>l z#;>v~$CJ8mO-Y({2XG%!ws~fFr(ct6(AV}m>4@+*tnmav(K=abuEy-eSnkm;d#ld) z#4%N9KRO7%pgcMfs?4UzX`pGD|F-}A2ZdRjGUTpBCi(!aB-!Mf6=&2)xnmMo&N@+P zU@}6XOjDdAx(iAWlHUws3jw5*rdL;=bJ+TVm ze>^_x{O%0T@F}rZOAx8p;Ku^W7(Xb1)n*hz?$)~AioIGd80n==f4 zIQ6%Y=h7e9Pt@mtXVXOvwHD-fLUR1J-+Cnsg;z+?Bsha2-w24abQD520xpjSBOdQ; z#D>vF2T}sWblkbm=5qG-vJgl?e{(c1U>aA2L_(ZnCT>`(v)ZE+j3TL)}76Y zalOXCP^`2#l3`A#8jA|1%3}&&;%LDkviD^8zE5LSxVIr?0W$@ms$N~MkS4(x!Gyz|@08A9=zLJ0&gSwW8np@-BJYcjS zh-Z z>hY7{wu@)yd$D;K7|w*+RjetD@H%d|M6Da?n_X6v->X#T_VVZ7BCh^(x!U&n?D%0i zhYq+%-%L|FHnzc0!e4c;LI-@uZ(~N`I2TzhEQNehf_Hxp-fo3?tmRvN=sHVanJ(wa zI^!N1|DopH2*G(Z52a*)t5Sn*I?w+gi+%l$Z~-(GgS(7Lg_0zdvAI&=Jr$Y%@T=>` zg|9IE)IMiefB9IoyC39FRMz_HK~PEStu=qt6>9p1*-y*XVY6;d4wosaS9 zszr@@GbH$Qx&X=4Fw32JW*3+nXfde_Xjv5p%>@K{Hc&DDnh_s2kE`&RK;k{%x5zDH;?XrG5lSP8yscF61o7YViW)og zeRks7v*!)GAgPCICCs_yV{97imX8q+k|ut6s8_oWD{qj=@ zG_{g=0FMozZOO?{2hZH?b$5NPZ)4-_lksJ=GpyE`U8HQQ%`N#0jzbTv+P3vvu_4Rb zyd>;ta8sc{94S5KTf~@~?=J;26Jlk83?%Y@deNQmn3f2sf{B8`69rovn1%Y<4F!59 zmXUyuMUuQmsh7u2tj!Sa<|OHOrmN$*sS8A}M$3+F+(3r{A*0=e;o}Pck;nH+1xM@; zp+UjsQIVd|_-rY`Xy(kpsPeTu1$Qc0V?VZmMS(L7Lz<2P`;w9HZ*ehE<&)iIC0m6Q zio(C2ul-?C6Cn{~K{Swby16u}(K;MUufmmc`d!kgY#Yhz>9$DZ4#RkwoieW|XN=DZ z@h8NTWL+>E_x=Dqp-sE;Ed(NZRn54#=_aal=Dp9KclrSpphoM-ujg!I(jE z9VeiIKb0Y_M9`=;mN<(+UDn`uLq@u*<5+W=kp+%}`hgDXo%cF?Rqjry3spOd_a*jM zB(pD_Rz0B+_H$3c)#yN5>ypCN)gpwbg%|SW^o{0aZAXd^sq!j2*^4r)0j$K@%fWuL zVBP#5Fg9=9LvPs(l0ZxFlhK!ir5tFLmKn_GGdXqXorDI-=2?diTlk8j66sLOZ(ZVL zXR#8U!&l#lavV_&-&jva8Sfg~GUVck3i2=l%L)>EQ!$GOQqpL5I7H&s#+JLuEfnC2 z!ov#VGiF|`ccNT)+oa`s76dPuW(RsBt~*VWxQD(OSu5fy%Ptz$d1Okr_a9-YL~2g& zx1>V&4PWh5SQ)~)rjZi^^&hyx-znc~2|}-UER&>j>g1~BB+*Et*WgV;bq#2|fUN(H zvR?!MvGZ|^EXF!3y#cm1U*^T{pb~L$9ZElvhOvt09cGo9yd4XSy8M0NQ!ExXQe~w*KI3=f*JEIgzadY zm^^37&0@WJZp%-SwRZjquXci6TMPzETTC$e&KXt12qYeengXtC>vbT3*;3&uxngWT z78qgoGTQ)B73gaao!oKk0hj*l z1fnTw1~1{SP2I+6WzWRwGMBp$4|mgL4?}UaHPWYeI&{o6b~Y*2j)-20&#pU(9J%%Kr!~Q(0k;7Okg7~4S)Fn= z_E>q~{&H=V3tea+}%HY=t8VGx_@mTsZ2IV4oz$`n#i0dDat$(R$ zYrvK9FtZ~nO82e?0-JkKQ-LYl(MUm_?m4VAZ{F8FlW4?<(fKy%yG)4rq}}^~+j2zz z1T>?zp>n9)K>5;d(8IZ{(YsolqQnBiO@Nw*0y-=u?h^#KEsJZR^IXg=&?Q^?3z<+p znpo-?$k!y*54!-Z5LJ>geU8y8%-to|VyNCwc$aAPg;RkxAFKvF87QgH&TSrI=^R)} zKt-aEy5su-_q8%>L3PZ}R)KAXVEvFiL=_U1X4OsSDL zM0_hlR3&H@2O20N(iFSh9Ru;!PDw3A{jY;NthtU;c3nQ^49-+YtqZY$I~ZD-^H6a? zJqH57mh;Sd6H5!hIaHgzT1~>1cO_&hdK8Q01swWsqr<&UHD;zr%hatJrwuz2g1H#T zf){R+{LxFN|Q>fgFQQGnhWa!4N0yYYG<(={NYX<m zOud{UggB4OYJzn)NoHgps$n(adH&?Kxn`1k=iV{Qk-8?vUJ5_d?jEQAd)U6PLA9Sh zFbhUGV@g6V4NO6zJYw=md!T1gQgK{^ua}aThI@aby@JO38Aub%(rp5{c|E?WPhEW& zI1d8;h25>!%{6x7I7p@){kHay=<=y`6|zj+=$~2&P91a0U03e4lh738mDnU7wl+zH zy#(z!QM`z#;~Hu*O>+S&V}uoP8)W9Jvb(mikHxmx+Hi3hZu$D7MAdVKgnAv04+mFG zk6kPPAc6>R-EwZoQRn9;?XB|}ZJC?~*TrkKz8IK`Wpi-#QMaALff4|IyN}X^6?%V1 z3z8CO;v`nb) z3rr=>YOx68BHbZk>^%%ebjc7h;-yPBJJEOaSO0W`FYrr|Ms z-kC|ONV|O_0^CG83TdGoNOa{Lv0Avbw23n&p&*etFgmyi27WrAi;L_aAf_AE(CR3v zRpA3j+kfJBpEmBZ=y85|3c6lvE5IiW*J0rh#i- zcDVQ)1eKi3bW8NKl1a__NZ&-k9q#zxOmo;)ya~ga{Q5+$a{CWmAaL7b?-rMtB6?DZ zh?s?$^X-x^wU$SGomq3 zU`5wbEO+Xrm!t=^<|JZ6b@trk#}(Pl6O$pP6dKFN=S`b>0f%}t;L1r;-XU+@mL!B2 zO1<(~y<+y9n}d|j6-|jBlOgn)ECjV!H$Ea1Pn(4FdAUIAaV)%wYIt--&+you+<^?^l5U0rHTs6Q8YW$g7*4h0A{!*V_N+!`; zZ$D9X8Ru6PK&yv)v8%fRnr1+Tb{)_XM_$!Z7$uYsNxAN*jCyI+sd;?n?RXBg+F5&8 z)b(D09f|Nqq4EuKQ6)GbpADny(lWmpAUImxAKm^iQYDAq?_Ki+TZRnKy#l#36@C3o zBZ@Za-?(p1>D!(Ojsmuqj3w_zyl5MNFhVDYwGGQ;?6xsxPP5b!Zrj7}^jiV|4f#48 z+z1zn)Dl#2R%25Hd`rva;`iE?ppQ#Q9&Q4{o1_SDK!OJ<3Kg_UA&?G#4tRkn~KGr&-zevUQ+N6O1a1RU4pYbYqi% zo08j%yX|#g8p)h9K>Y!T%#8X*-IkI<*2Q58q}_+8B39XCscq*N1FeI1LGgVF5uMcx^bb;~wL?yN?`DrBt>V-T(tWVQmf~oa0E~x`b z^{im=Y;X4^EE%Cyx7|4VXuzSBEFYpE#lbK0@wv(*^K7tX;Y#}hMhc=Ym9}sNNwLB2 z?UzU@Wkg%Tg1@)%)Nz9Mk-mQFsMGd!>EeBrbU7&ioZ(HYi30GL0kN;xOokRW9MvpJ ze+y7)0>On~ zyK8L3jq)QFd&eP-1-B%zD4?jbwa^5-$ZM_bsnQh;JCjk(ljYH4SbTO;{&pX~D{{MZ zvpV#6kXI41Jw8)amGS}?a8{W{^P1N@5iE^CY$HNBSk7<5HM9PQ1Kb&H(b|`ako}8( z_Lz%f8NcS;AKhy)wMIhtq&3o>L0KuFjJYmq8A?A1Idjb|REs%#apAv#&plziYF4<@ zBd0oywMO7oxPUJQBz3{X|i3dpFedHqD>QeF!e zLb~!c7MJ4V3FcZel66I2ONlSkpPQCz2w-(J0J17oxd8jvTa;WGdj1if?mQ}MYwz9e zaZF%^W9NPYOh`ADbc0>fswz6#Z7o0^=1H6&q$KxwE=^Q6Oso^7Ng*BF20!tVI00=7 zB%!UGdH%2qNC>D2oGdE#HW6jpjb+-7U5;PK;1|=TC+`Mg^GM4&{H;Twp??IoAvX4U z%C7<%ky1;iQ|W}?#Na^BFoU-*ksY@Cm|qylDRxG`YBEbE+N&t)sVhcZ6#{HeFHscg z5M$g!i_B51U#=8jv2Q#lpB)1xR>v&vM=E(lNyb{w2ZzH+P`suM<3IPqECL^0A=l>s z!gh{PdR)R@XdNx3CsM5Y1e;N@9&ma*LfE`h(=-@{g!ZH!%xZd{X<}e-g@7?teXx1^ z4DG14nL{z_&sY=8I1IwCrl_lGM>(@Zl&-|8H&UarbfHq8%rL~{OI4Lzx8HAkJ?WOE z@*?e7dowV$BtgNeB#QE9KqhK$>UDcT9vOY0ckgt2bXW#h9n)N zd(g>Tvv3SzSEHPYlQ25oM)vjcy!4vke1D*(fM@&UVA)~SiIqe^(QF&zW;k_3(d;fh+MMK?`NAKI1jBnJdG! zc?DnlBWgc5^j>)oyZ`YefT@VI!npIh7T5faqJdwO_RHA8yqF{Kx-7w(S*@1A&R2ly z91EeTsBlcCL8y@8_%bEIJPZf}r9RmJL$q=4hB<0nCG7hIy^PA^EB|gXp4SzLfgUt7 z!Ij%aU9040fn>TV?k~%P8*D9ZEOxGX+8p~e4kA88PU z>@#MC*uI4u-o1P`z8v<{Zr3W}p~(+?4rSg> zT$H37pus)H=MMm00WZq`bSr-s#{X3h6s*(R|>c7L4 zzlg;@itXi{tv}tsUlirDaNj}K{_o~r6F=+qd3Edz{_B~)U4i8P7hGZbs}BF);L5*1 zjDV${mA;9k(I+G@F*G!=HL%n(u)|fu{S+Y7awbL=I=C9RTwJ)+@>U9#CZDq!e3F&_ zTA84U?f=Ftf6e<(>GD@Y$wc4Y_^-7v)6xCC{6F7)0Fc8~pu3pTfe<-qt|J0>bH=MpTz&4=x=4g%;fsd|6mmZ9{A*a_TiPAujJ$yGl+XR8H1@!cud1kW`M~d*Jbv+KggDBSimE z>kdlrgNS|rQE-5#^k~8>kU%385I3^raIESAaF~q5cCt709h7llaP%xj7R(!m3ZnL8 zv|oz1i%q95wOI6biI0mvc`f6D?+ap}VG@lgsEbX{nZ)&DN6prUH=p#bhs>kv7mpp>2W_<%)hn2 zM9G&Z`4S~xqU1}Ie2J1TQSv29zC_8FDESg4U!vqolzfSjFH!O(O1?zNmniuXC10ZC zOO$+xk}py6B}%?T$(Jbk5+z@vLsAq$1&?|_JZBPE|v5_+;$7CM&y5+(lt~(@EyHKD2@TW#k*7gP`?+oZNYwaDtoT==2J7dN z|DLGvncDJqbcQ1CU)e7INYr4Y{og?w!hcWxSE9yeKFxnAj8EKR@R_Odw_NzQ{rvYs z@(=&O^4}6PKK;XgOVIfA4}VLZe|QO8W_r56Qak<=&-@in@=rW-=47NCt2RS5{fiAy z7FxIh9P3EIP)jcOV730F;4NkpcNKtMJyrNT9xV zp2N^gO@Bvq>W1^|Z1x23@#+Ww0s!}YmwZsVt>7;Y+UU{-13uaU7kq5_nfgt#+#0o$ zhffK0ClDUk3w~0IhXymeHv#DWTT6FWgmUM_bQb`$0X_gKdv3=wAD?u2I%+M=GV*(g zP_&jwrJkY?k3uvyHVrU{#B+%lqY@I7&#&xPfp<1_N8o7`_{WO)SaQJQTQgK?D=Bz- zzmh~|aWUviP+!LAcRjlR9RLI#G!YSSIDq}{UQfT&`8_POk1xQ1UTxo*^jyJUZuwmR zQ!6bxeKgXv7h$J)7#2Z6Ix|~!y1YNut}e)B;lKbK{IvlhXpEGAkGPkr43lOu81G+Y z$~^!ua1%0d0TPP6Jk!mqlW$_Xmyu?S+nJ;P+T%$SptM!BV$H9*)3qpGL`g?DC<{yR3Ti^Nq zg4UpgOXMzn(qn$fV{Rk|Gz{Wzd2iOKlv!NaeK>kM>oNVY9nmH*scicGyQ>oY`60Rl zA6NL0x++*y7_gHBX)y!Ib$&4(7#Ge@fe>l`;m;@lybdseuLNx8Jt~wnZ?>=}76ljy zIGhb|j)%$zo$wnI57w6FiWdMptdbw|vz0H)kDXv%m!C2nWCNNC9oZkS4$0b;+23+9 zJyD)Z1cI0${O2yMShZcB7oH#dzxh6O;pX1Z3kR9!u7nQW->4cGYGsAPckjiIFA|ir zqk=;{obK?TXk@3h#A=ekOd2r<(m@}PWfK|tBt3CwL_V*%rlHiwWvK*p$y}((u_BSV zyqrDO(T72Y=-Xn)4u@&va0xT%=Gj+vHEfC0B{jL>&QaDsz0SlH0DTV2?7V3Vsz1mJg<7Rr5>A7!v-q}bdDDRIUBfZuz{6~4jYC_E?v61<#0(vt_u#U&HX z;8)`e!>U4|KAZ<+oZM&(U3YoAw5He^XbB17={|j+YT!(cY*Rh=n(to8{tP$tlpJqK zM=?~5Z`9yO{FIl_d&F*P=iwkCo;FTVJ(5mcg=<6B+gJ&(QCW6VURoD3y2>Jy-Gpio zO?p>abWS9hz!f}~20EN2Cy!KnowXZ@VwOUOg`kW-(JS4mGSP^|GrN}xP+>29!Ku=3L*F<#+7#KioiT4^>Eq6Iz-)Cj z^1`nM)|`tYnJRwH%AlGb&BrNS@t775JI4NP%Jw7(Qq3BRtOa{hyJ9-aefg?+l$Q;X znYEkjii=-=FHRk&Mg*5Z(a*sj{#wES&Fa)jt8Nd17q+uK%ij#6G^>(Hy2V`M}6&iHU1F#EKL7>j}+!C3!_!9Xu0VKxiH9lIyA>-o0a#_`hw#5IW z4z6E0m^u+lh*1tjjZhL6MGd_e=lC5?qXxyI>Af@$uqYW@7((~*kACab@4$kRwLi3l z&>|ilE;?DBp>r%koM(mh+%1GQ5D&ZTj?1b)UK9GIo?Y>pXB+#eG7rqrzei|^W`uu- z&sHHIp>Sd)AS>9lj+H3IGCNj z`+l~RS(j#ruA}=hW6vHx;v7_9vtBOsimr<5(oYPng2&?nAPZilEn*J z4n-YA-^XV4!DZ~4ba{+}z@XI7arVVNYxKp-OT8xxlj4M^fIJEC!Ld1=Q5h6Z%e^6i zxA0ZnD$j(wm=w&D0K|64nAr1&e$&zia%Pk^J)R*z=>v)xY|)pZmJI_+pTx#tw*VE4 zT#+ehYUPerqlOtB*HjR}SxyFTcbenY^7`ShEyH=QrCL^p@Tbw%O{BdQ4%c8gF#-Qhx)^38fDsu556mszpJQxztcL*OF7 z{DDFH#cw+l$Wn*loCafdL~YK5z5{a{2RpSi&ePo4uvNyWcl^XW==6ttE3;-8>OPH?Au!PGB`8vQ}~-O|BTTR#p85-A-$ez)$p zu3QPf-LEQw#}zai<%Fsf<^`XIlSF{o;dq^l3QSCoAJwWMW^TC>yQYX8FI*{fL zXr_%4^8`d-3`8;S+m!6sIxvc@*B!TVvs#jEjy#L*D&W$zfK^>fU0|4yla96tikek0 zV}A7f5-v&y6V{V_^i4h>uqw5Dtp(JReZMCql)vIjBgEpOwDMEv^$Ua#nsEE)Tlc)B+Wrj*7cWq~vc@hUeeb^N1*j zP1mLCbXJ5uZiognhP11vIj<-9{lC|TXZ|z@$W^*B0sQr+uJwd+e?}`|=R85FC$HNf zHHp#^m?8_}>|w8CV-IY%2ni>odKM_|`uEzYNJS`{HWZw*)&>I(P0@-T7TPRA$?xzo zE%l)56$okfjT+L3q2=A|CuL`C!sZl~{P!`49rc=P7uAsy7^v17OZgk2Xy^!&Bpes{ ziljSl9SYALJ=D^lRkKC4hyx4mc+W&DO+-ObA&neU@%TEe2e_$-gKUn-^U`yA<%i1x ze25s|5eu|U3`2-j?`_M<6t zZD%`k?PU_?_mk|3zeK4I6Ykaf9Pn!VVGmKP%4CX5@`w~ES5Emr1M`UV_r;NUw&qzyB1I63_0e#2E^uU4m3{NJ}4$>U-z2)2+YUUg4V&fo(A#4fF#RwL&FvxEF@9@lZK z_h>>5MdHxEdHVDSlRNzgYX43KRo9iAQA%FdU~^gNe1*pPiJG z9lRrfub%8rUEXR&!EQQX(D~7@V}Sa@?KdkL!3RAT=!LSUg>W5<&YF4Knk6 z;s;h(gV|8EquABm&+h{=z~cIAAa`)nQ&Pg-aZv^?b}5Is(1Q|vtFnW0(nO`HQhTR1 zRCdhl*1A_y6W-Qnk_gIYlj%pJNjtuRKNrHqk>8EPpCJgP=9j^hv2)TBmIyqZ&UP-N zOWUf>T>*PUl!7*vG{!UhN23YZ^}4$RD03D9XEYP&TX9ZiQHLabXXp0_0!KF7?w5np zww5X1vm!)wc%vn>y4RKUjubWXo}L0(zwJit!{lMRTT(`j`oKydQ7mmZ<@U5gk%f03 zky3cril>yATBaR{b=VmtNfJffa8JxU5P=rJpT)&F-hwCI<812QVu-rvZDvy!rmV%+ znddyA-j0*?@P>xNcJ&AfVfc4c(5;bMNymMf?8aJ8OoYA03{eF>LrOUbZuQ=hzdv0>X zfKddiMk29(85YuPT4I{Sjp(C5t_=l5I+(lX+q;{F9ibl1KHv-s#u+iEenKQAf1_c++Te~ZI^UJb-vH^3S(whttUP@)$!wl( zL-5xUu1^ldQw0S>GS4+7p&WxV6AK_wc7efw$I znvC{pRkw4`KaOSn-e!1}G0DYYZEHc4Qow`Zn)ha`Hp}uw2!-i_oE*|xT$Nqo6f78r zl?14qTdkXJc%HsTdXMJm+c5GXP~AcX`|Dp->ewrE>1 z_3^np6qlfjnON{%p!697=2PjZeVX4Pg(~p=kpyKh={OOQ{2|toM~hw;iL3AOf#Nh! zF$Lg=f{KrDyc#J%l|e#naCZQ?YITVJX}**mK=*=H_R+(o<>&lfEf-O4@25TdaAa408`Q_RFFwdlZ~9ta z_V`GTo=XP--|1969ZL(e@i00JjT{?QP^si?ACFCLu}&C~J-;zCgSg5Ci$m@x5K>)a zrz(wsxfT%wup;Kr?=<@oCEQ6&}=7jh^&-k%lU(fkb(t zkqf#lP1Ir4ozZt=)i*wMYEvbX_WSa(ybt9V1rqc#r9the2)na3tYidLICV3-uKb!X zJ|NK?0@=vsaI==8CcvTVaW#TTP%dzK#x)I}XH!d+Ans>H@5r_~F>&l8A}o`2OE+;q z*G>U-8}6E`K-|^NMSQJ#99xcVP?gs-r4YEJbv%sJG(p?Kp*OJj6Cut;2ImcTNA*ph zx&a}NcGWr=apVV|zm4AwcyL1hK31Ql__8`eR&a6`Np42Bg#27j{;poUcN}k0OP{~1 zh>ZZ~d6WUPRBz}+Y2K}Koc)@up#inw9ZH@uH5wu+_J}=6 zT9{X}{94vK8=eglgC<+$#68J+4xtu7%9YCpO0iG25Q2|9XL*ZK2@#N%NcawlQG+H#Y|CqN!11F9DUxMKpQCu)tRMg zE~b%o8O}>AMQ*Q^h#<8{y}=6$N@*XM{kh%9G-a1-RQ`(@Ebft&Y_q%$&lE3bGY($; z3W0~wV|~T`XF#xJ@$-`Et|FSkGGedH{N>TE-87WRR;L<9GwB*jBJZk@B7^;%o1HK? zsblkA$?S?c9)AkygEer;o1m}cB%^W18^RbMD>T+|-Q6LpPqcet^$F#C%nmcorV|*I zL}L<|it-GB>}I&>#;&u9D-(%U`}?|*Cw>PuR7ES|h_fYbm3brf(TW~7b_qG9kDdO6`xW=hr#AF@<$1JlM1h|Ju}uUN;_iSYIsiNfP_BphPqnHPhn7`Iaj1NY3S z);O_F36BhXTMbSH9Rjld#ok*5$CYL4)?&7pY%w!4Gg{2d%*>1yGc$t)7E2a0Gcz+Y zOTS#*Rb72fpL@^$b|0*WjL=%zx%bMgl@S^FjX7qq*c@P1b=1f{$NXb?;1~Hc&jFa6 zQ$AK0mdI_Z!q0K)U3MtC`Q9sdef`!?kt{Q$DQnA1>u-I~@}weWyGSCwqw`?0_4XHE z+M_sQ+eFM`6KNDme$F^7z6I#eoha{oSVf^3L>(DQyMdy(->R$^*UFZPHT>AIYq& zWxr$jo|lYPEVE;sKj)a%NOKdv$!J>pP=0+JwI%_2Yu%gd#$*VV7l;9N_eBx2fPCX* z#Ve#d7(^Up@*H+5Brsd(Aq;EnnB(5&r-%)YtwCV=rHeNAN3i30og{aPfska`> zE9WWkx+)GdE?l)p$06j-^91fCg2gkRw!LmuD7m257|_G$*q&#Gd%x6= zHxO-^a@w>pzL&;s7nL6r7mP^-K1n=b=fE1lJMU zr1Cek$$toc+I#m&0M*`h^n<= z@arx`171f-pG(lEjD|@uQnY~fjRsHuYKtJBOLmq7Z!lpe~&`mB>lnaug!3d9c z)=0co`f3ikS#VyN;XNfAN~Yc(G^v1EUlL!Peol+DWL3fSmg!EQn4mS6e4f5#viq}H zM6?z1Rrm8d_N|8Q^=%x;0AOZ@`pLuz;UhDLL_b$fE>9U}IM`_VfN=*F)fw8Qhw9^- zBh367BYuAK5p?BHm~QOneR-M~k~cvhY*{R?rL}%B!hZ72m~F`o=Ay_i8qq~X>IUdD zQb2Qw5*!xI0gkr)z-{n;wmrSG9t%_zkaE!j0|opND;2sC(=YKq3Iky>9b#r&D9}= zNcL)~KQ;!3ol<76yw@!z0Ylmuzm#4}RQIrI@|;b=v`!H{mF*PTVdCLz=6hgAJ1nSZ z)d8%gOs8w%8pAf;(Nj*B)m|6*W?%4zb@Z@-_`aH$cC#DaI8Rb%%5q16BW+&2d6{Ab z#vWC*(=B9M3$JeaiYQRW*g*u)wM4b+{qR6C4CB1eS4e1gQ}>f0XO>8C>?zSBUuadt zt&3f>Zn>-gCI9QEaUEv5_6mVC=_Vd~+O&JFhvEF`YbNH-D?6JLRRzaQuV!oKvfg)C;HsHd7V0!0*9mhwmHUZ{8hQkgq)S;1HN#!n)De zI`JHb-hh0}u2KJ8d-q5B`A443$U^^D9{i^uk(r)_^{>f41$lqBCjMP0_y5D1DE&WX z-2XHk{*`F|@3e`QrdE#jxHNjYb_RMDxHL9));8954yM*tx)xCXRi^k6DgWWK`&U%{ z?=r=YIQp-koaG}ZXQHM35HNlO=Dz}7Lo0({H|vjc{27@4DOF_r&$;lgoc*s{STlOu ztOpmq@0`0Avmsb$n_n6~o(h`n22S!BAS9B{L?N7Q=al2;GL^yu?E1({(D>&5TN7Lx zfcN+$j7rC&4nu|QFqm%FBKVxxkG1$TbEcZ`5L>@sPpZb7P9d34zzaD3xmagb&PCQu zf$~c-JLdm@8vJdq{0GnC|0ATq zKOBmG+aQ0kBY!t7vi+M`kskL0kocQfkpY+O55VI$Y4Dpg_)Qx8CJla*2ER#z-=x8B z(%?5~@S8OFO&a_r4Stgbze$7Nq`_~};5TXTn>6@M8vG^=ev<~jNrT^{!Ee&wH)-&j zH26&#{3Z?lUndRzNI(B8Y4Aq``fKvHl@&zu<%a(4f&y*T7U?*Fw+qqkW^Eovyy2g`ttd$H*A!A45A+W0OC| z|M;`Hp~J5O_&*N#YvPYX{xS2^ifIwABx%k9a@l)H1DQ`X?<2cSilO+uvn$;lVOKqJMlMs z28=8R>DI&OXWLnqc4~$-a@im`6qDX6I%~S72KJWjyh+w|SR>ZxN`>k2iMuW{?PQOu zEbAL7BiJT9YarTQd8G4+hw<@_HW)PZU!4pBJQqKqQk58!{_)Iy%QRI<=d~Tq?XM&5 z+Z(ggdhk$c45C%=JSMlcYbjmY9y(5iA59>~i1w9OMI)Cvxi%|W3@@HD3q`>Cy#N-i zlU9pjS$Gf#FEc=|+A;){aRJp$TH7yM`2cuQAV)#qCWh3vRx;yuxh|Y)zo}e*F5V>0 zt^20|-8tvl7^VQF5rr2(5|)R}e`gr}*gAjS$p00?@V8y~9~cHECR{4|KXeIU=I~ZDi*b+Xb|7A<~czgZL zmcaJw9rpKbk$+75`7-~sC9wUQ4dH`f_ygGalUev+82(Au{1b{{VP^StjsKf1!P!wI zUh|%M6&Fh!1~)?>*x{>lKP?Px4-AX|?CK}{90#Z=QBnx}a$E=qh$%#ID8$ei>+o~W z_Ir+fjrGT8jr~XcOyf%T^1Az<_dn+yKY@Sut@4RQT0zK#4aNiY@#~@oK+$A^&Zgy6 zQBjalQ3?3w-whIa_rSYJo8o;f!^g`jS}TbXAjomlrR&q&h7FKS3P6en z93PK>3ib^G`fHy2t6czr0YE7(Pcag&GQ15Mtp5YNZE;5{5P}SvGND_Rw?&`+RnTyFRPoF2%F8Ur8f0i>%U13r9MpAd?feHI-UXW=J*czA%N4M0!< z9qt2o?PMenK;Cm$+PNvf19v{1_6$cajtk(oXkY*+pm*_mk|$$)(i3G`2$&#uH(woJ znPljW&qsiO2j7n&@PVUW0N_UsiwFj~!6Uh02jD<%0oZb}ys)r9WR&Sad?pBAOUA*& zecbRoK|HgudCZZYQotl@zKn>0o*hE^;(31aQYwKG;_VP+#g{o4-~HLW@kTq-t~4f- z9Cs~EpYG8bAfYQ`t^F;Le4_zwN${p&6x0vo8}!#OQE^cK+%*7EfCa^aeK2BQE%**; z_=oB63OtZq(0d@(ykVaJ-VD40G=w4W9dJNggtln-nReiJ4Fo`601{GkNSlC0UR^m98C<`L%=zjub`@;NJHwof-fT*DdztXHz?EtybsR0pymy)vz`^ zNO_ibbvYPtcZL^~q@V^606#9Tui$~XaAgl5DXv_V1ItvjQR8?7H+I3K#+GADcb>~j}wmpU7EImAbSB?7{Mh(psl zlh!(o5Y84(lUJSDLR&ox24KWX+Me&tK^Wl_#@jtut8Rm+1WfrRB!H5ch9)EbE8+0C!ykJLq(4k9VYb3L%xlF4O)g# zx{+8QiMJ+Zl^x2eIiG43QB`KYYw;tI50sm5sV5t2X=46#IQ+mK|y3L^1Z?~k9n!WI9CdQ==%zio3L&U+z zpY&Dq0bi&boLtY2C{73*%EXY>^H$ssf5pp~dlMN?pT5{y?*zX5JX)7y z!0BNXwQqZxuw2$+}BtN6p3aPf6iv)&|!T#YelyM*KF_SCuVIuEzu zFm(BAKw8yl*0J)ltqJ*ADA>YJ``PxqKwrKs*ZHnj;v5bs^4ps^AJ%B$I$9#H5U&Zh z9?Y}dDx?0^d!a<5DIUAq$j5@VULZDIlyc=pLu%YF)|qqd1_!UDTb>p+WBSxosUfW+ zIIHmmzS0D#U@nQ69(6;c@zul^nj6iS4e*-+ z-b!%Ri_!Cbu9Ov-FlI?GrS%>dHL{5-h93ow2H{JW#k1HowR;CuD6#HCI|$dRVZfWk zU*80HCv{hmJjGTc8Zch!#hEAfN{VHU2xD3Axd>7mq#2ED_jN3@6*M3;5>hqnfdr-k ztq2zgUJqPK@7*i!=K1YdTRE*pzZJ5?oReH^e%D@v8V?K+OChJ3wx`O7&Skdl>x)vn zjs!g4ZO@>8j16=sn76kY3_l%kU65ip=T7$-=JZ@bY|yN5;wgb;*KRV-C^SfVy}OEM z^cr9nIp_;Yoh&~2E|6D_yeL6m&j)$t+dsP0fUKY*vTLZR;i@GOXX3Ne4lmvfgHuD< z)`Co^XYmGOP%MHHL$yvkYcC-vfJ%M z?fF(CnRyEOt#bTX59X+$eJz^xG`nQsiGKpP`Afj#(Km0|^& z1EURg5YnBbgk_uo*N1bHtl_)t^j5w4z(ieJ6-@GpTfP34hT*GT(es?>#1?9)i!VA~ z1wDb>x}__~TQR*1cYr#b%&}0p3BbT>!MWJT$KThBS1xr#2=^F1GR|)R#jh>OqcKa zDt~zOA*~-RdkI~d$F_ha+BZ?#Gbj&Zqzfw-vn$2?cnlmH*Whr2fGnJsG>s(dJ=?vL zy}`kjE<>~a5CuD7gh-}GpkK_85qisc z@E?P+>=`i^ah3dG|=A#Uy0<;&B)pVNcMXpl5+2I+@pl%x*ss7{08_)TOK0x>Q*fP=4DQJsn& zv#_Zf?#Es;7LOj=3uS54<|PXf*gH4Xm}go2OJa)zarUoHix31^wj;;fXT7pR(a9mR z0)f3PTL%Nxk~zv;-dniMWR-Q(i!G|mjhj<_soJ@EFt41;_W);{+&lb92Ylxgz-Uyk zBS!RGDwLkyI>6*z1*b8+tGhqjV>dMN8dL8ltnc(Kw-ROz{V}RCuaK6Dp^kt%JB%lg z>?hmo`E3U;6}ga{3c z0~*Ym+2B9VTD-qQLdp^*To7RHa1+ z6ubj4+v)Em(HOo2W0(240E4QN8Yu}$JtARf{j1$ z^@Td9*%D#?dd}0n-1{P30L1tPE7Dh>>+p%69bf9(wBuOs0ED9SbQZSrMNNGy!Ji+P z_MStOjYX0HbE-(gY03f!4EZY0Rdmxk4F$1eqA^pL6dp`+36;Z~TIw~gk$;&PQq{TlsrPuqT=2-U zirlzk@-ussf{w+bK``@$Jfc{0{~AFe=dfSR#sN&vn1%6~J2kqn4x54R$z_*I((WpV z3VRy-sSn%B;x^BkJ|dN<<}(9!V*b%$YM<^iqfDRjy%lkrLG<8McD)8iTlm>S>16ol z+Ta);`i3rAFty721_ZSqin4-f9_WRpuXx)tuVo=9Qx)pyk6x9!jh)iq{Q(aMz=lc< zpL*R~*afoT`{ueP>r?JwRyYTsUg#7!18%Pckd`9vj9QiWf$GAKW=!=MDuWL>^fN{J zfsO;d6ZERuk~Pd7&TOd_#vbQS7n!EP1kTH)kO@2liZ9ZqjiB{$VRDEKg^^}Npyxvc z6`+sAqL@fHSQ<#4x-&c2b%8mBtXgc^(@{$lyh60RHpI2;cCI&btU}mp(u?66gk^iI z_>NI1JXa`{q7$NnOCg*tVs(LAxTlSac??6PKb$|JdlcL4*)A%FhxJTN%)_z|;zMb# zdn`(sXqY)iyV9q0j_Wa=rnfCS70j8(DSK-I^;#HQyIZ6(;8WCG_AXsa9IOW2Q08$2 zO@}QoyeFx4*JowFDKNm`5PkLq(@c^)k%GHP=dO)xQ7BFbr!-f99cx+`d!xT)pK6yM zoWD=t95UdYRjS^0+rRalKB7K)Q^o2e>`rBi-7}A`KvT4!yAc3kQ5S5jRZ)?fVYd47s`(L z-iC2@kFMsI%#dacC!7G>`DOs;X97I{7Q70u6FoQQe9pK|kq>7fTMuuPVuTPN=Wf|5 z8}?y`a(zXJ&?$Fch@N|WLiv$AoRLz#?{IuQw2Wn}#KF9(GQ>E-M7LED1NyCg0RzU& zuy>MC%1HtEZMHQ;#eQP0DAPwy^#ut5(TzFK*AibH{F!*EUkSj?gACY6BCWhi{~i31 z6Cqr73UjI#*+IlrYO)VeR~`nH3<|Gm(8D~Cy^`wtSz8^n0!Zd3Z(@DG6PxB-iL8|t z^k7cii?YRSWi&fA?~ZuZ-kw4-SNWXz2*Pzr!YfAg0F!Bd(e~c%81+>-GX?*xe!J;R zmAnlAvYzB{fb9^=*^EMx&ohys#VnP>2`aRp5!+(g9K$5t+L=f3pAtoPAgH{K#!fJ@ zr{>FoNy`)aH({`%s^W$lPp#qWL=lx+;!RpRO`6{9R>lURASbH}e3to(?mhb~-FkkK zw#X(oiF0JCGPN_ZvY%&L%(2HhR%2;ER=q@{jYGN>j~jeVX%@cObQn=4kd8R8Hg?iG z?*vn#BDBWQ0#$yTRz_la=-2EkR1a2B;^4-x&4k2T%CffUsm+X#s6hQJ{l!sUJv@jT ziYP|b>K-0zS-m9_OWpfT4}5dDp!Q@JI%f-`=};$x<{S%}_T4OJl5F#a=xrIA;tFQ! zOkn8?J%s5SnBoc(pTXYXW|T+V-FZRfecM8R@6FFeB1j7<8`bV1U0=p0B}4kBV`X`$ z!jhC(-GoCLL4Si;gzQ-}yY!)G2R!#FPTvIES$ygTBqiZjM}dm`P2!9y`Cf3sC}U7n zN#MTrpfw-#rooh<2*qMIYi3g(wXqG#n}jv?{?E5#81k(IH83WYqPZ%wwkL?lr<^UY zVPKWon|2-$KgwORaKq%a=cl72?Re+w0GG_&b&%231=+1{`Ha{L#<#VO!sMI3F)Tde z+R@J;;PnKWUJ;ZXiadXOoUC|~W^lN4%y<$RTxMbDsxT6D^3fxq;z!+wl=kF}xaqQZ zW))}Rs4EvBf$!;_t%r$HurO@J2)}<3Vz$%Wll-~1oNcWNyr7QNV`-0JS19(206zi0V zJDv#E6rV90>Iy~FMeQ}8DuhqX7WqnoIz_}E3Avlo*U*@4&PyW|7+5p}0KjXtX1MTy zx0z3N4gxa6eo9lTMr1TZ4XliW=YLl*@9mH=3+8@tIYm_WG9!W!r>7D=KM-5^DtiXW z1-Cqa$GTx1HEN?T+CbYySnKp>r_6{#DM`mh+rj&IX+yJDAM zdj@Bph<^_iyY@-l)Zn(BtC+mOAvZ~Kj7|0O4pOT#1!LJx>B&`KMf;(Y2Fr;NGK9TH zOu+t{Bzc=KLjzJv*KvGiA$1g3y|5_P`C3SvtWUA2f2905pg87>;GU1fnbOp1_7?u> zIqN3|vMhJfQlWB{xa6HEc}McN#L;#Cff)+JV}jSOfk|_ntD{*d@zod%X!y<|Xbz+u z2;;N7)#ed|&hRyTPOJuSw%ABiBR&sUsnb=6Ph{9V;z`RV^|wm$mV2w|UF25MvDeTT zNDo7G1*KAFTcrD)7e^Xdws8~QtfI`Pk#}nZ&>Xp!GSyM(rirP>`~u-4Ol8U;%KmLf z*3RGO-RPpMI3a6{K4Wd*&565yPbc;%=s8RdmAi8#YYc4uVSqh9A2vm)0)-4obe_Ie zS?0c6gUf2am++Q*`sMnjeB$+O4IE!W#vL$wC`WwUD(k0O?7GZ=$}ukKiPqJde8{{} zy#8`gKy8*fp}2kWU%~;{0TaThmw*QSSan%YFYBD`uBMtz*;B7U6zN|afh9u z3Pzm%O%9FRhZim+wz?KN&Yo)QVDiY+E66M!+`(adBI`#%rLYP}OS;PG5So;^KJtB; z=F@t7@m7G!p-~6RIDK`5D7^%ZeCwRT-{>ei=7FA}xOBjr#j6#8LxMAOP^kv zE(gyV?o>zl9qMkY*rR>tI>9+kEtCzv9{8QUb3EijVss*9+ zOZnDj>JDmLxJ$m>ltVVN=4L;XiM5%OVp33 zHD{phcndL^y{!vY4li!<(3@1G>iW&@lj&?F?nXQ)x47QtiyuGK`|7i583 z4Lt^-9A+JP2T-Q*ONSP|)8bMTGpE<;hb7AkO+^8B!$Lk)$1}3aq|5#v+q6*r z_c%ThCwmXuUMQ~Oa$i>wiKO`MvrrCBu<)7guzEjHL%lqLLjRmv(9GPP?IF~T_&z++ zy3)@L-yDBgAjkY%2%$H#K7d#PF%(#J2hZWcYWXY?k>s3U ztWG|cUmK_cO_G~W6Ms)+1-CDuxTY&9v1+ca{yBCyUNa<@1r`)7{E(uYMvZbl!~CF~ z6Vnr3FGzk7PsDfgjD#nXP@6#l8YwQ%5U#?m6^2WF2=Uo0z?f#9w&^k`+V)#y#>2(d z>f)w2rftHUl^RPQ1&z*dK20IMqsKeP%;F(6+YE7Nb~m?18*0aqFns?7qrraKQG<$C ztTPBq54T-C zqlwN9qf;DIO6h@we-v)wD9U;;nL03Bv2Q3trk=2i=F`>EHIj$#>9!N$JZ93FJ%=Wz zQ@L^~)PwNhVj0bUccjuVP>WfD#<{LijJv#t*nmVaemU1W09(xjzLC%+Q8wRY;2C;v@a21{#~ojhMcyu(@-pY5B|gs4LJefQDi0opV596{T7u_cImT|q z@%WwPE5~||Z}lRHkEhxJS%%JKSE-t<_#soQ$IZ%UN~S|x`?BSl=2`e!L|=a#5a913 zSyj1VX`$*d6;Ig22}_;8AGPw&P$}crVVrSj_uyb=!QQF-%r_ypHrZr~MROMt!i&tN z?d5Z_X)3g@Uz09TozG~>Z&r_!DH!1{74yN+npS3P*b=DJ_6{|@%?Sm0b?&l@x>=Q! zsnmH3k}VYf0V7UOe6R23Lw{SdEzksU;x~bt(U;^zc&ArE#9*zl`Y@c^E`8v!bZ_?L zV8K7{C2c}{e^|k&#TD$i2a1%$&X%{YGupcXc2;R@@Vs(GFU&Ne33f|lse84_>EA#0MbPkO!k z%)aESuj92{Ee3LI=pAY8=X+;9kI~7j{=Ol{hM}tEratjIsqBUZdG}-u`K!CgD+}~al@s4;7ZqiIf;hS`%a2blH!*vMuC@dm8VL%*U&=p0 z*7T$I36#H)qI~rcHk1-Dx3waJcHzKM0$nmt0U|C53%~6f(be%MwUNK z3xDLxzd~qcCbqv+6n})wzb5|?@swX90bLtW!(X@x zE;I9A-~h|VZ-0RUA5_Ud!GT|jjDLXxbj<&}VIRT1;V;nSzmNm}nECTn{)rC!TH@b) z2lO9L{5RpjFJ$E(9UEx=qz7m}{{8=?2cT$#Ep#0W1r7DB4GjMcPdRmUW=yx7w}$sS zpaQXtJ8+!A&Ar#kV?c+|4@#MI2bokCoPo%N=F4q3m~_8E1mPoe3u6$=ymxK8=i2w! zfAO44aW79fUUJuNU-f!cGoKqBD$5(EFc1FWONzH)q6MGj1A<#w;*9grqG1aO9-h#^ zkjW=kH=0L|Ux@$$5mcyy=q*eO8xk;&jvkcui+*8<53t!8EdU{ik024R9wHEsPqvQ_ zE7(B;9uOHIsSj7SlMgXYc94&NOhDNcgt9f^OeaFbPfu^Jix&>KfhMkK zF%>$jiQi--JX*MjIxWnE$O1o}$+@@xhRen`;b!rRYj-!dD_>0+4~YD30V(Vbq);c2 z8(dO|DPTSD7jd)+pk`jLqG6E%D0EhSS?``F2=`hNbVxv2AoLkjAQ4^I#_m&GQGjiX z3FTRUT1~tQp1v>MFfKmr;=n!+yx!ffj!$Ut$ZyZI3v;NOTTnsn0-LZtT*zR6wF!(5 z6Iv$N05A>Dh7dtH@X0nE*gB*z7XEY(SghzWv9q8Z$%wBBSVO3gjzW14GBOL5Fpu7y zI-z9{4RIokjl3Hs$eWdzDA}UlsJ{5#?7gol8MAgFojn^{!UZlbzLWcBG*h(WVIJ+l z$|?f9sptp>yboaFn(zwp^72Yxg7B^Yd`l`Pc&D)ewpNC?`1+_G9cJ{k@DsX$`OXb2V^zJ+F#e2BOAQsrOCSIg=!DCjf4jSpmGYwZz<(Uc0JrhDQ zq|bZNA;Cc9ASf#%o|BAo()&zlZfEl6=s*q~mltJ(98#iUv>qw&(0a!gfdrmjrWYn1 zT2jnhe_lRsO;P|F2z%ETtEF}Jk?19Med62sbOeLV;eMWWdobXb!Cb`w)1R zkS^Wd+b{E@`fPB*YoC{IE3++EV?;;L-;9`uhrCa{$2mc^K&yz-GX)pKxot~5rd;Ik zzMc;@Gm|p){k%H>C8<1TtR8#~-w~!acJ131+KI8SI|r|B8V|(`re}~yYSTV0wGNmk zIb=?M)>I)nh$v%%_xe(J_+{vsTzz&>w4=iux8}_GaL*X|BwRjtHn%VJbk8eGkR3|Zmp|LC+jgq+6bf?Dkmci0XJH2Cfeb{<|KOW za1I4=9f%eG`zdPuLKrtb{ONcR+5jXZCZkN zMG_N?pD3F-fjgO5J?3g}q@vS!C>K?N30V(J9&L*i@^aCR6%jR|l94V081DCGP+Jwe zzs)XYyVN4(C@;}gi6efG^bC$EMUy0z9lYIvV<+QFA)Bo;d^3TnG|N;{gsT(HfsHd) zUB>xJdEo`0`E~RZ1i9TbK;Ntiulc#w~wBUV`3qH>~cPO%2 zHQRb10}4VSr~i7|w9{6yaI$)~u)UtN7TS1G>mppZ_hofRn8R?ap2e zwsm}Mkh7dr$||9kba0${p4(SPqXESi@LXzJOqY|J*C zx+E&v0eP>kzoOa7Hji)vrlB#R5RB@f^m>YPk<3iD!ETr#1PMiCpFv9B`5enFIf8$w zt%oIHr?76|MC@57zb$x9m#vBAqUOf)2D)taD9z~NF{;6klR{g2IrK0e{+3OslHo!$ zSzS?2aPsEj(hV#%T0J*$@M9eAqDm9BvL9Le&f7J!BU)5#uhJ5{nsyc^|H-ajh@{7h zw^6SpGww35-AM4Kl-GHX|Aq|_%ntl?d#(d(V~X&VC0>65Ywv+t#Cw#<`aLMz$UwTSulWPmKqd$X2WfkoO5EkJZ))+mruL{f)8@i~#67b+?wC8c5)IX&j zv-RVS_eH>sUgse-{!t4pMU$B`>9Nqz7qtN=`6a(~}ULL8pL zGyHk_@=j9V3)j{PXA3ibrsG!BmUPdLSB9c`BS6Hfj7n~#;U-6zx=L@@aFkW%n>I!p zE^{I2jQKuckA|P3sL0eWK&V(kD8xbDIl6rq^HShvcLGJZ=Tr(QG=pc&_~*Uup_~+q zB&YU)T%%rQK;TXLO-m`Kcc&fc%Qh*xZp+`XdHiw%^2%HI$-c0Zovk!S7#rN3V$z89 z-q0Vbw7y`Pp1D4h($P(1IL?>SONK058F57_oE_B#d@9|!9Lc18qq^WJbYo`758(M07jLF@DUHZzFb6{y<@7x&)=KkgmGp^qgXqShU8jBtP>fGyYdiG z#V&s~$vf`l=qxcJ?9ZQsJ>{}AsA(xXgFOhk3We40fd3>6oLZsfmZJ~#a3ZWMJ}p3X z59m^65dzDYE$Yx6{I-j4rPv3St6x1opv*z0NGf34aqPNJr}Cz;pxg!Vmp zNWdV!q`#32u7}i{@#JSMKn~g6%UGV&E@z8;)OS@CCc}BZID(cLg~($zu*BvgkruYGsmXQs_JRnaE2^(ck9Ja&&0E84 zYhC-O)T`g@SqK{k6;-Q!TPbb#VsF&000}6LF4c{+y5=cb?<1zc8#NaLqSEC`ln>W% zOx2nF*4mhDZokPw)JcRC9UU}wB4;pv_1JJc?`_7ia$o2yRn`Mq{q9&*Z&!lC9^G~z z@We%Un?mf_2#4EJOR8fllpf2Wlm3M~t9^o{U$ALb7*`bS+p?l{;7>$bWzsrM{iL@R zQJX^$+dQ_##z`k96`!vPlLLIN(Cvo~l%{IRldGAt$K7DT(9qndi7S$@82M=-7#SRi zy*eRKc>?zi{Y*=o%2g-%>GUBDYSTZfXPi_bICPPahzHmG+Yj08A7tUYf3U;OjPITw za~#9k#~2rPAc&U^_*gmSYp`?>L(xOiLD?$wV7M&`Ol)WOzbz0x?PCSEm{>$VtFrNP9wRw&zV{u0GBr z;aVRLeNtRS>8*1~xTUt)fp6L%$y6LtZe3aLGf|U-HggZBs>yI9p}er5 zKqMrJei5>Z!MP={z%xysbc2&9UAgIbzB=CPM5ZvJU*#7i8(EFXvy z#-Wx{RcJHh#0`l4bB1?|XyYyi;M?p1@@A()*Jh=R8^oNwVeDM2Ey=skD5Z1PX_$qI z=7BW#MrwvkAKW>c_QNWE&HU|qvl&YsfSSrwLQJhBr1UL(*6Be>ipzFB;wF>8K&{(eXaPU}_nG-#UNU{P5 zcsi@dRcq&Jhe*uS0gRD<;%5=NWz_&v4{_j>Oo&(-tK?3>u+Ldk3A&v#N3kD#n(W6yk=&qyAJX4J^JY{9rq1W`c+okYBX*a|%e!YC}p+Zf`Pgi-MkG>jwBW zg1)B_XeSTBBp3RAoiBx$&_aoS#fGQ^`7xGJ6ek;w}3HsG@STYkfE z(KVpB(|jFfDBr`7|Lt*^2y4hZSfL+uR_k&mmYT|G`X@lwN`+N5m2Qe0w_gTGU& zny=fzFMfX%yz#DYv1e#av@som zr)S=RH=CEzA?5CSUp$}fOrDg9^F;w2S2`?U%#q8UvBw$W+a=pItUUXK?{xx^ zICTHGE8ZUrvC>rv&Zz-Ox7)yt6(hC&fvQF($EtwT^l}W2PlMgZJGVKl=Wr=lS5sFU z*uTSvaWDXFT;LcsX1V&IOwy@FbZDa6N55~sKE$RCC#gEBOrqsTWp#DaXHFsiu;;tM zoOcn^e>h{6LlN&|-7;+xcJL&xqlzNHVn4cJ-_}|3ub&cjTPrKzo;|8`)E0Xa^l<3c zxrgV`Duw^9HG@bi?s>^@u0^DEllfU7*Yo9QV{GT{n&C6UO#XFUhrX_HT@csjHE3fog zrm~O|Lw7o3(OSZ^ERkelNXyrVxVbyZ>pV(CPfw3t4a849nb%f<&;n8%%9CE zt7TtnO)dpQt=4sKug_6*xd;Sg8OhG$EE%SN_j4pwS(-Nm!DCcLRYoP7DZ@2_d7y1Q zidkz}tHGAX!))csou^p`F296|WMuNhM@azM4&E1{P?yRX^li&8W{<~gN-flrMXjVp zP4Z0{mC6)?TR9EuHxXwxix`;B&f3ppSHN4PY|X-(zrV<`HrR_`b>1brv0u{OgO+0V zj@m{Rs3ficZ^2GWEI}6QXzGi^_uO7TIvo-r+DBBA$4_3uC^2DJmCH#SJJbbE?{jQ& z$;Td4rbaciduDWuJf>AT4&=3kl{_)&xyjNl%gj)vO1j@j!=Vy=$#`CWDMoCv^#H2% zOEYdN*a0G7{Gk20PT{ODd63z(-#%kg4K=DDl5)fqmZJ7z?+;Tze{b0*eH`7jO0hb{ zLLlbg@LfLRU+I;IyQ=T(4KCCtB#_?_(ll|~5p;ujwssH~`r_<+TIvRUefaH;V)@|xJK+NQzgvxvm}sI4!4 zV7_usm|F~fp8KvC+)e9x$};+0>ZXN&V`{U6x}%_{p7~9Uh+-RvHE$kj+7$?F%i2RP z2Bzag#46TFN<`+|>wpZkC1~UKUGI;6o7xmd)yA&^AY4Ep9C?gUdB$Gra<(fE*V!?= zEIXm8QW#24H-~D$l9{DW(rmjIockok-{NX~$`&jaSm^1r-v&uofb%IbciF zH+0$!!RD&mutZXV>d$a>bM;9Z5-Fky7Zt4Wn6^iJlBK@MI!Y#jNLd4m`mp!vb%tSxu>!eihgmZeeUH<6Z)ExEMv3*R#lvR4tuySF=H`pkRb(+U$EQwJ{BPT}FA=Dm{{Tym+{1XgXnX!t86I6>^ zQg7Qg1*vq=nN0ygjjZ@l?;>q60!$x-7 zEgV|M+=CxW@&P1$Y}JejtO2tv!v^9{gopd2c|oN~<|D7b~?~Nh=c8(R8QwcHTZFvWwYfcO0lW z8roBblIJviVsQZZ-q3T4e|h~(`h}uP9k_Ev@Fl9@tFp6eCj;gz%Oc^j3m^@g6-Pim zac;hp3aiGv0|xZR7gje8>MYPVtGrqWuaBJC%zSsP(xdS>Eb1;*be{<0z}ee9WNC6; zWCx@iHj%#X#M&jA$)Z-V?CVXOaojA>p&&2leo49T%R820>n#ugQ)|Ax!wOLz>t?HU zIPCI;_}R*w@!jVVGdDJ(fkro$Lbm@}@tWtEmupTDe65t;G^T3g&YkItE;wjtZj<~D z96wmoK99SRLD8i;X!{=X+!7qamzf0%CYc20L)GzYduLR`BYtW&%{pj6Kj7KK-{yt2L(xSP$NEONzy6}vu= zyr+IIY*2xBKY+y1FX)k2PdZ&tXqlwx0KS zKSAM;(X(W!3-p_MijGbNzbM}wj=tGWnb-7TQ8Ad@7 zrGQCJHKROIpK5%aHm`5k$y$05`0Zc8i(2|0z=VH(k9}5`pAkLus`ggAmaD|vd`{h| zMtb`&Y`1HP@S+#45^jJ(Bt$Eqnh<2kkaJfdn_%tx5ggeW)s*~Wg}pEyI21bt602%c z5(zCEIhZFadtk1&O>UCXS!u1~CS$q&)(7?5Pl>QN;*T5Gb_rd5Cq0kxUP|F;F<197>B^;>Z-tfZ+%j5S=nbolB2P#y`i#7~2hpY~Ojhz;VP1KOa? zbr5TTkT5-VtZ;7wi!8G3DZPdwVQL7!&6md4(~Db0zS0a4%_lXZfSWjwg<)EIzb-Q& zpc&-B8+Ct^D23i`@M8^r$FF>V;_Y;tGi<05L1K@uE z!auMf12a9#KMC+ZDB&Ngtn}>viGcrs3jbLB7Y5Au2Z;P9cKi#j{;v#J;qMtR1O1;4 z{=Y!r|GSO=B?~hfga0b@|AL!;3f8|kriG)Evyi!gBf-B99{7b={}UVk@}~a{8~@|U z|J>St;88}#Ur+fPxBB1cs7_puZ65)G@TK*1cO#UZhBS=e!IU8>1CQ!xcp z@0MGBzSiS1-KRV)6A9XW3x6n1fQ3sjr>V__{A!6+S*7+RZk^8xJj6jROSAGcBi(7@ zlN0odp0{!>wUe#VqkLpCP}WsV8HS<9k4ZLrFnf%@yN3Sw%>MaC{dEoe4}kqYKmET^ z^Z&oW*Z<+%|218Jf&QOSi9asLzxevUx%hwZ^?#G`|KRKYm{ahZum9%jzxn!azW$r9 z|K{tz`TB3Z{+qA==Ig)t`ftAeo3H=o>%aN>Z@&JUum9%jzxn!azW$r9|K{tz`TB3Z z{+qA==Ig)t`u`93`ag%S|2tp*gA)EhO#hv)|I?@bSHAv-!293X>i^2u6@T${8HxWc zS^u(@e;rWqUjY06Xj%Vqr~k67|6rMa@p9&0bKrm7pjdt#k>yX&PEWwh_{SFL2{@QI z{+*@&`KteY8~+0${%Zq&Ct?5D-~OkB&HQ%~_7{d<{pvtUH`(wzfSmHL~Q~#j$a4* z-!*J!7v*`IC0as7uw3S+AcN!YfFTJN7qZ|O`eB&*A!r2xg8Y<#5*Co4fS{Bo@~sN| z{DLBaSkIz9n@7=3lb%ysW|^rCrWYNL4UZjXtvwZSrkU`_^Fnp@8KT zBuc)MfB-=V5Fj;+cLkW!N$^8n1UUoLg95-ZLj@25Q|Ix#b;QX|s;{$rvIA*9tOFd`TnVoBDfqMEoJH9WZJ5Km0mIBZh=VMCZVB2;a2VC#B?Ja`c6I{UyF~%4 zj!A^1!(WLQW(fbARv*#r2Z!W?&RAo@X}5dZ^=PKuA?cMz^3oU%$Ur$#`!U&FMui*Nk1isyj_pX9BxCwm{ zFaV(+XWJ!-@`ehwWMc9yh-h^KH=x&W*cY`l_)c)yXh!!V(2wjaLhv;Y=o1Mio&tPG zFVp_&0fAYRkf#IjRI(c%LRREKE)y9Dx|?9vuy?l?pck3{X@v{=1IljyG629AGq-EL z87dHnXBy9+xC%lTVHPpql^D9e+Xn$)aSR&~<*7QvFJ(AKz8laY7W5~AtT@UFU;f@a z#1J2&?USxJUpG>qmMjIp^Zom)arBxHIP>VlXZF?BE$Td@vYPzN(Yw;eRxS$4v@btF zR~$q)9Y_#>JOv6+*bbb)C!Q#L%v-tmFyCHTKF$mP+$WcgcJ@bn?G(N%fVSUEDE8B% zKBxKmDiJ`HpKS{&2w2U~JO5|38}8G=O_$cSp2Ejk@~4>~)uyd&S9b zm7kXkAAv;n-Y<`U6!|}A;JU)dL6AYWb#?9i7}t*-kYFH!cQH==uU5 z>{-A8W6bGl^^B6^06@*dM$JeZ1QeAoxOf)uu|+zn?>-pd(I*vD`#;zLj4Vgf%k7 zj0Zn$@iY+jxKhTh((*8SC-9VOR58v$s?<1GEGS%Yk3Q2kpf@Xel~3yJ`+p{nw$6QN zD;S9ce!CV62$i-@T*AJ-k(onLCy{Dzy}J9pN3kkyf}7C_)3bEx*y+yoKAf^AS{m_P z)VNMoN=wF~5e+smcqGLH_$PInZobMgFRs}|t(W0bv3vRJ31_Nxn&OusV|br(N;Iz* zuOmvX)U`c97Vlowt~xOT$|So-M#^vmwO@Uq+FWB_u2)72pz4(hRSOvAC(T%Lh|g4L z#622h%v`{TSe-^XFWD5Oet80wzC}o!f6i3q?tDPF=~FA4 zcBbsgs48y5{1TrqIN6=sT>nv8qWV(xiHoLJG&>f)^o-fDptD4A;N!T4+>Q*hml6`E z@SNvaC{02(=^)L9HjU^_x&>^BLdSlgOgkQ}n7><*i>huvcSfCv-kBBJby zt)YHN5HNJG>5)TK0H%>m{4uu~L_mfT{qa+43KFDm1A8{+u1wlq! zc{h-nxWRaJ8K%fJ;XW|}!OaPE&BD)%C$WLtvHo%%shVpVRB{&@auvqRY;9VMY1z2@ z)%bGr8eK9RAP;bcHg1_R7t*CdU6wc%8SoR|&~L?EJomv~hVgW33@sIHWSV$amxq%T zfW%K~g!Tsqa_RqBev*g((9-T)qddBcJr^|UqI3e^Z1x>>bVD8;s7H@8D3 zT8;{x;ImCfd(+;1oP6^Qbvg;wQ{K|k?&aIMl1Fa|x;`YlS5(lZWcRskZ>ej<;zeji z?!x3J-GB#Y@N4jPG{_P?_??^!iQ{El4T)of_TW1Xcr(7M>2BV7=xQS&>d$~i2b&_Z z3dIcO;--G+4<^M=-QJ4Mu*jLMjg8OGSWg#oaqk)NDY4|EJ?Lt6&Xmh#T~BFGo&M(B zAJ6>}{jQCoEgs=DAL)Cx!jCRIlC!Y#UA&8Rl|5R6c)eqrcG=X=E`lJ;6xQ8mI8m3e zbTD4JE}b+)1(*t|__}V}ovkhUSOneb+x4TT2?T!Ogr677UfJMCClI4jS>V9X8y{53 zS-4C~ZCtaE>)J#ktZ=GQS9m7X45W^2QM|N+2&2tO^YqC@kR23mFWdn(lq!a|2ohR&nP zIoFJOG#Ip@ii@6`fk>6H0bZqkQd$blU5Cr!2({-eE&ZVdW>K1t3q^YPXli|{S8%}6 zj>Q+aNlQ<8#?;}_Yh*=D_X$LQRlV3~Ee zdUt(Rsl3Dz(H+DK(*-JE+u2rZR-jhQne^J*Opd2250r&n=tO)`o7geHI?*gyZ-iPm ztJ5?qQ%Q2W=L(u4R6LqEoK}&L!?m#0$7{00UN>i2RbxhmRqArB26izJ(A*^dlB%d`@!M<#9)YUXPRZ++4oSPPqIxA)T^ z<>k$lMHWIVWIi-{(hj_M{V=DVQ{s?lp%aJx(sCFz$Zs8^T!9^^fx%UyMoTO>66!0% zt*X>0>xE9`1?pk5`p#$7+n}}DbnSEdbTJQgj)1IFG3h2^{fe`1th-+pdVYel7d?bx zS!uSGjmQ$Gp1wf*#4u+GMmsGKFHGvIyFgEUiJ*f+nI?x|t`k}Ph&~SXi#(AukA?P* z69kjAGy2`h-3oZz05}FXw%L4-wF`oYaoZfz*i?2<^;g;j)-UCVj^I8lIMCT|FD@A~ z*~K|p&~cV*Kd~=NTE~3I3&vzzMyy|&?G6TdY;~&m<=gZLg6`)EB-mQZJQY{tktA8M zTz&8lW*8FLq$4*DTO5J)xFC4J8Lk6KV}>T9DGA%|Ht_kh&DRn;buO$U+<_Z7syi7) zI4r}SNU|CFVdYtFKXvy#ww3R<8%yXX!yqc!Z{SeHD*qE)#jxxA|~8 z8?Lr5iG_#4jtx>AL?WbiRRtXM>Sm6qB* zV_Y8Ir2Y;$XI1*W)Rta+2|;UVio=5S{7SYyf@#83jVy-XnP5e~W@62F6uv(F%ibm4 zqbFme(e({{*HVg8)1G2jLaoMw#M^D2cI=rJ?V!&4yy!r%&0b^CrCfV`z`j;6DP|`) zUBQH^cyXKdLr`jJ8np>}1#;csoB6k0C>~L1&Ms7$X1%A`7Q$*@$=na+m?ttH=gqXZ z3)Ac#8QMiy&IxR8mfHmy_(>)#r5|>$HoVAkdTY<4uIZ?~eamvLnDFJAUXqKB0%xjU zstkO2PA>XZw2Z5%_0y0X5W~mUkd<$_+Adc54~ZwCqL>z#tR9 zWsON2aSuRt(y9%Wf@j2lnJS&k?ar)dmY?h2>H|Ta2i&^y-&iWh>L~7QCly^x*|_uX zzg~vg-R#-zSFqKc)-<-eN%;!`sjSwdXPBnTIVGg?S|K@8Ss}}Y$I@0B`bop}+h^_^ zoI-CoHc*g~PlP?kHKfwr5V>JkPz&5ZiO_OWl;o`q%9^#_GVUe$NH8}3mfj#08kQbFy)fd^B|lY=QLKu6gksyS zw9^_jL0(Hmd1wP-KbLD~d9fqMdaI}2av+@$F|wR;ok2zTEmHj=fir7if2aIK8@aHN zC?X86bJ3QjbKs%qc7#5X>u!kWiF1G511_FoYrABbt4AuXLnjVI>y2?5n0Qjiu6@Bm z;6FwrwTSCM&B9=!NYh72X zU5U%bci2%d+CyG%m%_d)F9_p)VC7?_Tf_p;QVug&pD+Q!kenA9>Cl?mw&dr$?~*nS zPJ@>CHnXq(Cy9_>N4;&IqCTaBqJQ*$L)XxJx1)R>VQzbuw3ge zm8e>4;Qsh|df?0Cimhj;vE*iKazi}n(ZNna)8kbun6ZltBJc9Op85{;r^n=}Z7bu5kmN@MWuPdMi&pL>tdh^40!BWD^sjF`I-O;Up+`UtyL}gEv7KvKVq}|%0nmT!TM2fWgRdxZGk+?B#dDTjCU>!?w~$*VoCogjUCk%}=g(dl zpDH9aeguy-93Ds7a-s)-Tad4SJFb+D2)u0hJzTXOm8~aymronUr+C$E5cFi}i7v@W z3Td!*U0DC|zztO>q+XAN*43B+7Wh&N9ky8dYNCJyx4Hp^`3*<@G#xLTBGGnp|JPUl z^9$){mT|(+@Zx>ewAfG637R}G@5ii2&3+};Pvd@=U^+}zLruqfd42uMW%sKQ2}9R< z<-mmoLz(#dD&G4I>?bl+_-N?xz#!^zLkl|BevLKwU}VnhMqE|2K?w@aAg(|c!Oe$7 zr<3YB&>vasw74|r9eMrhN|s7?x%fUn{+lR;7Z8fcQ+Kf%DEee5BRIAn03DnU*kdM7 zIXrKr2l4z*=OJS4Jq1;26mR9gsxDkxI(}?Nw>USDr|#AaJ=KiXmy0|+Bx*Poap!l^jwJOtBsJBuP&(PO|J|Z!{|N~ z_t}SYojAAfb2Yv8W5o!X80gBKTaA#kpMvqCsvf*wzQ_cl&3*1%fA?J-IsK6#Cnbl| z27cyFT50f|{l%CWRhygy`tz7^kgr7;f#xLRhW!?UQ+R_Qk3)Vq_ilyD*fzr>l|9LS zlYY@V-tfBBX22O0U+XS&lJQ8LC%(MyZNA%V;k#ubYy5^v;64uPQtW^_&s} zuGeV11#1pV>hsI+>LtziP$DOkP;w?$fqVfe^iPO7DCx#+hWnsfs zg|R6E=oq3nj$tUy7jsKSvDA7mT}v43aV=vO=wi8gVL#kfI4y0=+(x{X ztYX3h^bU?wlae>@V<%TQ=o^d?VR8a1} zTfX{yA!F0QP|-U)E^`wLA3(c>?Nyzz0yn|e%61cvZXHDpVmMWGyBIFtBKD~2%`R&c zxe8AkxtOdg{GvQO9L^qGR%i~+UHnPaGe~^q<5uj3G00iCC{H5wBRSrcY^F^m-aX${ zE|~4Y@eNie5@G5R&rP)K7`iKTu$5BfOHmYWNZ1wj(`22HBI#6V5gV6n%m+vZ{L?1h zm{@TcOM!^su9P=xT6U=gQz_HicHX)zd6HKRi-QgyaONt!SIy_h(BYLeWt;$Yzc?D4`3E0P@ ze%`*~#jk{A_=FRKXYz9F&`?7vS<4!l-WS9#5*~D1>W@@YGv{kK&wcINGYv-Kqj;`F z2IjDiUCM!RH535EQ9va?v=R%!_JN_V!eT3}rA`SQUj4@YR9R|ubvtOwC%1S$N{_H% z<8kN9=c5FO>(X{*0EUqD03b~YFoce1ZF$rLvwZ)}U;ZP&1TFx4 zL_!POD+f`Zii;PgIwkO8D=@a$7{ai|Sb0SWV8J=57IZW4I8D^ntDl>~28YEIOK__VqMddvu=a;SiD_M!2sq!BMp`1@yz> z0BhAp36g_A<=}CXA=j4lSy{%2zcA%})WP-2xG8HL7Py?zAtt zd*`G2386`ds`ovuMYqrZW)36Qh4b@Hko%{i$U#6h7ZyHrQRq9BXgL!LeW*qSY1^5Z zcHemE9wy^hoh-&#pWE)Zo2{9G4qkNuLr=CQ_h5R zcQVhe1JFWbytv7QW6(TxWyMKeN~`yo+YNDwn=ONBG<2~-=gvNgN#9*Vvt2M#`Br?$ za!_W-yKFCR*BlwN1BXDD;v0$P+)+*kWZqlq31U9BUYFRh5p7Dbs2_uEiua{6k?6vW zeYy|HoFw$3UYzUR#>meT5M7v5Bg6pxAr}S7jUeF9Z~O7DJEGZOq>LA28JcIZNcZa! z4yEhgOTQ{!4GNp3ZNeNmQ!I|WG2bRrclMtfq1U%d=Gv)-PY!yP{^0O8PHEDSDgaBv zQ{gZCT>d_qNRw6gHUzscE(YaHGl8ylgr7R>bm+(Qrr>zUA)4wrFkp6H!jtg|DDx$0 zxG62u%4jKMFYROm(T)6joK9twty?=~AL!nqV%W6ICS&7VgDA*4YltxM8_lnuSvgL; zHyF^Fq6g`H5l^oRq5FsXkYK`LDS)_p;9FnQW9je5@d*3Kb$+UKW-BEoa-Fsb3)JVn%f36_u1Zt;p{`ON+g`>-V*d z1QXp}COg;LxpZ-ep-`sy2oE0Fsw>Ri%2PgVkjrP__`*jEyWRhYraNdn!ovak5%U^?mB`9}*p_q+R{gGAx5#oy`LY}cj$dWU_jXs{ z&lOt61^N;tmn-~p&1E6%s1;d~XDKOQ!L91cC0KU-a5I#n(bQ-xzTLJnYSIEs)SX9jPBDneo)gH}7|g%NDq$XPrwNG`9>940 zRLU`LKUM=0PT%sMzPP*GKMb}fdYmG(D}+qgUg(Y zF`Wp9Dde6Tk|;(9jtjG7QkM;4Tm{tT`-a9^-fYBqy=X z*`($n`Ncv78x9-Igr%zp#h?O~YRz~B^r#0>v5dV5W9mdTLPvx;bOP8H4a^KQ;_mQ}hG zwHU85d$HUnOJWyiMn~e^I**xSBWja^OY*x^Gn5VkDk}5|wf9|c{rSm-)hSWhYu&fQ zDUQN9=!8s5cl9%9(SslXRTPK@9*!?9LwopSN#DOB1Rm588qIRa(vfLHSIsw4$y`n1 zesgBiUf8gUr7Pq!8vx<=WGf{dG>6E9;%{+jtUBMbpu&gBsD2DloBnHh{J+`dgia34*;T{E$SxjuL2 zltvz1ksUvQsWHYUIZMQXbaAvPJhrV2(&~MyJ)Qxzl_rcub4}C526NLHChi&*~ow3W3ZJKaTbe0usTYs&}V= z?#cJVkb(9$2Ck#n@XzvGZR3t1O_l0hG77w+{l-;aX_vW**2X0n&Gwg4{8hG}0JUcB zDEu&Vq|iH|VAtld2q|(v)ChqGtYWE(FsQ*euJrnkx;UCtMIcZ3%v?993!0dAsoZ?f znuJ>!qkx=zDRn%E*54zmZ2#%fcIB9z^ z->-z#G!NqojPgbh0NWV*avPW{!iw%$=qWL|aZBHdTxc2Zf~8M-qEtOtb-#hs=g?=q zcMA8XPat8-v`N)2-F|WE-nvSJ_;OUlf-^ciFoaavz|n{fm1|W0x-t@I%yl)p`jd8+888>UxhvB^n^0jG z9T~Lzz&C{amavePnB8)&{*D79FC-3`;$MFiJYm0kU~A0ZedTi}0hQ1_DCM%$H{vBl z&f!d1P-Jf&$+jt(<==O6o?vqh=dV$lj@)@tan>NSw@c))`UZ%G^}rPQ;xhkhh#fId zGQV_%HDhTSF3gQH9kIK9QqXYYH+19%3h??Y!(>ny?j6M9m}AFLRl?yTe%N3By$x@J z3NRJcoS>gNp6PJALlb%bosN^vKyrU-I#noHJE9yasx&Ectl!B6Jm>t-dx}!*%#-wN z-#Cg26x7KY91;}`q)|>me0HIGfn^EE@>|j=hR0}_o$NqR&NbPWM>?&|$hLuD+zWl9 za=amzM07MHI3pV$I)S9*w}T%SGl6w%L(VN%6(o|!-eodm#<}9dv{B}wfy@(5kS!jy z49L?Y_d?tprnysWsiD37b#z7)7M-{@2tkTE+eL(ImRlp zP8L!VP;Ei|n8~;>%l9K!@8MYe`U~8?gs>vO)<9}AEU!-rA=f50WvyU#Bsp)ivLIe{7ZI$f#E-T21IOsRi#_ln*9+O zFjBO$F|hq><*yEcKk60!pHda9e>S_*L;abp_!SBGmAPR0FHwp=Uh9ty{rk!P%jx{r zI{Uv)`|qL@zZx9=(Pz)d#`rgV_Wy696znX2AEo%U{8yBMk>fvl_5UME@!yPlhQBrL z8UCwr&+uQ3dxrmF+%vKN3WNNkv*CYew0Cx6$F^N-T$r6M>j+nwmmJ4ZVQO4dMTrMt zKq7KgC~q)3*T5o>b8bKs_2g_r);dd!vLxP(} z2R_YFfRF>|!dl&c4{~Ccm_N`;13c+jTFM~fh6mmyA1&oXU{RMt_APDh8yq#y!Z-? z0Qj+hO`s0*=GNRLn+-0Q=ZhcGt8dQcMllK>?g+w3po7@GhQPy6L8l4=A<{DyAg?CJ zOo&^;hH?VcKn~z*1_vsr3=<$P82dprg&ZQ#YQ!_Z9ba`s8j&4J=&S(@swULdLMX2; z*Xk_B4YH`|0%r)`t?Bo^sT{xKW`iEDAV7oB{DrP4o?@t#$_8Hq6g%ucmgp1 zR5HkRSaK5p7!i*@m~LF+jk`TOn+wSO;?0Ik83AKE#uc0sA%Y)-2p1ukkKdns9viUV zupJ=)@N?t3nhO8}CYabjJ%^_kF&yZPiL)ol{;7@}20!QyR5Ji#9|Tz7Xsb)@5rR87 zU!ltPGx#&By7|C{#V0K7D*NUWuCOfP0Tc}30wpO946p}DQ9p;q0P(3>;etQjZvo=P zOx@-iFct(E#$+97{Ruv%*C!&l{1x3j1mGvjcfho6O%VC3Y?3B0i6H%8{Hss4g@7Zm z4?7sX*qdzek4s2_c5NNMNAW8+rw@3xdDxTFM|6LFO*$F@fEV*X82L{)7O{*obc&K# zf8vhykB#!^uSmf;fsQ0`8YncRUEUX?tI*k?QeU5o=61|?>vJ}6oCZAez^W%P$41NXR?BtU=RcP zIlchB$B_0A-doSm(BU>N!iK4Fuw|3KXCHsPx)AF=(yZd-EA>&J z9WEnI@!6eU;k_1?>;~kTLUAFULB3^Hk$pyzhM&QW`CeX4yAjCsfN`s|gGGat6~Ie|lSS0%oqSz% z+~2pMcSuHSd~v9KA8nFDL6*|r#q@Nsw*oZ-`90*w`-yIeWM0_=ws(x-E8J|4ja`V!z5-By_5pOEl@woMxyQN881D; zfj}Y|yUq+-_0fr}s8(=$X`=tV25(k*_$I_i4?|>Qv1CR)(mKG!6CP=NQ$mQbtYneV zgr@FZ&{`2iPBU^c^^PuAbOvok!KgG8>Ht?GAIV$^?T7Id#z=9!XPe}%k;oe68)DsN z87+z`8lQcp@r1ou!86YQPWB+{EtxHz8rI|X0R3@}POBiM|CDcbz-(^dir?@>Jtcm= zxBzyQalG05S4bH302k%Uh3jz=M_pJY0nwAa@Txh;QC|C8D(~YC*5}D-Es>zHX2|s+ z#!58%FXwxYW_5NieU)ZLRpG$O^}4^tiOa9I!49TSrWc~Xjnm}zs6P`fj;eCqr{zyM zXuZuNjtLw5_`rXxh-`ic!pU4L$JhqQ&0o6|0V9Q8fmIq?^c5({L$XGtwx>IaETN(Z zz{IjJE0Z6nj~#~%4U|y`s>yn61zN806uj*&UK_iVO&H=mhVEWDWVJ;b847=vzY$r6 z=s+rveT+phMDTt7&Kw}<)f^MSb{5!@Tibu8%qnAOvZ%4EF@o#0gqtm!d41oBPTtb8 zyOdLz^ihCsp}D)enh8Ct`w@X4^MDrN1w-P@;?=PM06*#b`Mr@X=Q2ki0klDzhg7ua zri9M7q3wvYjAv(&tl<7A+#Dt4jcT3q2aRiUmt>iz60MF@W=f_N1pw@Oph)6b3P0C^V>z-fw$tB4VkBbS0 z<~w#aeRc%38!P0-6X6K`9-36uf_v&Sy`R|BevM6J0sq>#R(D3VE>M6Ds<@^vSkLSK zJt21ZCba)lWUOK2X9&90iy0iZ2@KwnU;JEeNUy0>*a;?M%)xG_F z?F!qmh}Wr`U(`-|I=y#>@$#^fP$&E1!*p9FuN0wi+$$uk1)i&ua_Dh&*2r;&w& zlqYp^)REP^M5IIYlSG@{O$7WPy;pVx2~JD*qMK-YGI{V*t6wk!imX$IKz4(mbG4}Y zpn|cm_L+UM(byF-t?VeLJldwP~0b?R*JFmxvVy_M|>*=k~B!z{Ma7o@gE zNjJMdP%aP~>X91w;t8esI>~$aT&pvaxj4P@c1^42EGR_u;Ue)H&Wi!*OLKV~l1K0= z-<)1+f8GHe7^2>)>M=e+S;mq>rL+@cxx)eP>3Q1P!TPX|dAqq*B^(Enp4AFjL#ene zx!B5ieyxWSvqG?2$`0Bhx@Ev!o+CO;_$g+w(qJ;)gPK-JB^NUW@yhBE?jA1Abq*s- zi}hEN@0D2<7uplNs!eJj`|cnDk6DVx!Z_H0J#?P^BHC2I}Q=crwA3fEP|dXH9VlEQJbmU zu~LYRY_CjZ7%xTV^2qWnMWPR8RHlMuTj0{1s+^Nwbk{C=rAsuCAxrQg2H19p41J74 z98H--g)8@_Xk+Y+&O>t;yf5}2qH2RDKJAKd*?6{2-t>#OnISRjY3*XCvbv|%JsXjA ze9~u?A%NU_U~o}Z29eo{7vhf`*72;q4@RVqGFNZTG{HE$4Cv*TYCoydiEAtEy2#SM z73MqRon-}}kA8ul7aN*zT)ANP*#Pja8*$}&ER&hcf74A#aon=QjG1|Gg`Z0(h5q%!gQ^3T40B1XZ&3izQ3uhHW$A*mQo=Ew}aMV0u z=3(oKhXYN>txC5@?_)S!usS=-7`|u?BwI7{3r@^UGlTaeVN>OI;l)T_rvd8ZGDeM# zAtRxAHr4nE_oL$QwZ{W4F2>N+bOn*#wz&-@$S6{|Jo5&$D6TDCqYs8MLpn6Zq_?o9 zKp-HVm6Vxi9Fgj<$b;9PQ5 zwmW2gUDO*5tXQ__BgZ~0E}f3&7A~uayh0;qgZ+gX_CY~I#w4{%LbIhgvDyiS7f*86 z-i#6Yx%U9PJnM2SUw$p}flss4AkD-CH~T5XtLL8oO%=~u?oPdoaB+*ex+vRD#7^Ex zYCq6$H`)H1g~bW`0JC36ea%|Xc>w>wq!>&SB?Ej6J`eBiDDr71&Gf3Nu1?bxtb)?d z9(Tq}z+#@8^Hb*K@`Ra8eFy!O+4lbOt^f$hFIJteQI0Y$6+t}*=NkcLDR^!BTaMSy zX%VRrCl_Sd1)mFgC)(r0ef2p~umZEUTV+{o;e6jwYUY3{NS=KKyd!moAP>@E*iKa} zO^8u$G{m&dls$JPJFVpN!SYQbK$xV7TTtMTJeLXp>Xn>NW-Q)D)yT2Nwxw`k^rtoJUN#j>Jrq5PmnEz&x#z~l^MmxvozY>P6Vbd@I(o$=Wd|5~2b157jl$77r=#ftIC zt3DC#hZrxnyX$o$6SZQ%>JwUhM20Xz^`l-NqRB@S4~HEh*v1x5GWziB6|KiGSpF|q z;VG?ZYp4&pY{)-uq5W(uo(_w2P~QYL6~ z4FT@_cL(BwZ5bg_PT*qwH4QG>II7`IYy{U9R8%+i}*5B zMSw#oYxqBsyO__y$@DI^ld00toa%foA9)MPlXuj8JIj0aJ&0=g69}VdIaCxknMc`! zF854oLc1{T%=S;4tnb}9hKc#av!fSQcQW`oORVnIV4dq3QeaFq@fYv68c)uH`+&5pEZZAK%~G+$m?RT@G_c`rqlXtjOEVjs{j!< zwa}@18Iw0`&4U{jhLVoFUY3AJP#uZmV8!8e8nPy1!D!r2PYI+V+C0xy>kUs8A5|H= zg|5-N!2^+R_%85RNW@~bc5yBj4hP#-Um=Gd{uBnUHJ+||_q>6mN>OiDo1X>O^~!)^ zGA^9q_NoH8;4~vSD0M*=myLITe4UM^#i?nG*Y4h($Q_{JlLL*hIn0|Y>qI|+MyZHo zRv%*m*SZXUjDCADYDJ{Pi?uGtqR-SJ(YpKUPq*ZT?;+Wb@RPZvss~4B3Dh{duXsTx zp<{jGzL7Nl%Tz2$K!E%+*`&poVF7eX)9xmjgMX8u6_%X~r02I*MW08{v4K&VC^4E_HXX&CG=ItQZ6r)7^+)B0E?B=z^)!?pZVv|I zYol1FF|7H)1_uuyl;*|TK1v{SmAN+hyapKp5ADDdBDVOgDdJN5S zewvj-Bcu~=_gj;;H7>n5BN>d{J7w2u@~-C-AAd*1?)F;JY8U_F}L`oS3J@OQO! z0&sEou;pL@f;iAc@bUN#%@r3c*mJ1i%@aG@k%&V%%ih(CyXS$X$kRF=#*kXH%pn%@ zT@uYG+&yV(s@^od&(evP)1RrCMprE*g@8EOi2^UH5#Hf!>X^Ttd^=1r0n~ZnlPP?SC!pVDW z;Q7kW@a$+GSp@!)qT!+j8z(W1PcO70%_Aht_zRvl$J7N&_{FMz-j%^-?JECW-_+Ar zDfnuS76#V^NQlT7+y_ZL@w^oWG<`M)V_F*BMK{Zzk^V{|zChowWw8!oZm>}Ctd1gZ zvIJ>fKyD%lpuSbR0c0}jH7VijS|#J?yrW>4)Ayv7OIB;V3t(ccdiDOIkIXy;R7NQO!71 zXvA$-kYF+Z@H%Shd4E_~Fb@Xs%136E%vHR?Fj&fv?}d#B=r2 zp3XL8_yzJTr#+#QwA-tbiFn7ta$_7J97_1sX$+f6$#4gYgPJ7jlhze-Es?!VHK9r& z&T~wXd~m3{dbnfTh?%qbYiNIo^s4RGf#baJ|siv-S zU3;+>Kesoo00HIN*cC>HSx}{T?xD{gk68;UjLYn|-YAdjJfmC#FW2olcS!b4Qr^qI zN5D|lStAvnc0G|UO-GeYQXjuool{rtG22~Faz5s#4M6!auKM1j)Sd4&?Ul;RRuC+v zw5RSRHWWU0wAG?l*FxBxhxRxk$3`+1OeF+INlmvVuo3x8%w$e5AJp18E|wKJ?v||` z6koM26`Wr%ITX3f8w%4b}{KxYdI;~n+hoDuF`kg~OL+;pd((E|2oc$C1SnawbrxNEv9jBkwF8GzYB${i=f?QV_)>IP1r9wj`DyASsI_Tt zo#x|U17D<1mvoQ*3M83L6Hr5=OTL;_%u1F$Y$U*rd#?aDb0kkCn-ZkXnN2?!9(QBk!{R-ikC6p1OQZC$iPT)rFeVq`Cmj@fa3+CXR5@0t z&rw_@1qw3*=ghCr4Dhz7J;7QrRj@I{;uOZuCw?VoQ3Y^9{b&Phx ze6W);xb?vOp7uS%ArjBA#Sc2HSbf{KA5 z-T)G)MORd&I`GUU#|btNp%eIp=KzHaGE)Xiic3bZK7D7A_H@*j zso4RQrTebjTC3t|qK(l7SysGIH3N~!2D~!tpm>N6rJz;5j%ZYChS`S-DKK0InQ&a< z9c62z<=PWjSp;UGZm|hhXkB+eY;UBK&yu#TwS|Df9oetQjn!{Qt>&?dTOFRz;6<1L}wpkh{g7)wZ z@+s%?K)s(U?IVH2uSu-j$zSyERLBzQ^|zDra-w#8;*x9Ek1ngWx+Zz=VH%?wydNTCG+*wtY^*xj1vKOu ziFW*nn%rsMnkh%!$}F~(@FM>5>|MkchTS+R&h#iMYx0P@$rJ^LMW1fen>28OzW544tzvbKL?3j{9s%v#* zXNnD1sKS{qF?<01m^w)1BU63H3kp*mbV(zdLEASON^JupsUY&|XsJ$Ghcr4|r5Ut@ zsvRzEDs1kup~4B9CN@2_JI3X?%Vx`i^1I_$P^;yzcyjdTja2q*^YZHU6kb!#qd8>y40n^!11@S|~wE`%qy{Z5(dv4|(cQfF<*gRY14ju4~>w!zB44tQNL`R&eXjzmM2U*eM zZ5!d&CK`02D$k!W*O8hkcvyZ|al9Fsx#bXs78^z#(>)~?50#@=ZrVA zX~jbv^LYb&_Q1dBb!Wr9!=C_;|C+$4KVvH&1-;z`P}WyZ@^|d~uR;Q*f1%|6B;u@0 ztp7sP|E7?D;olqoEu4RiBc~AeFm$vqrX-+~ceFEhF*0!^ps+D8l2am3HF0#Zu(Ks# zV4`Ip(54V{`9FnSO>Yx15WVMD?4^R9YL7n$AtcmLi336u4hXp9W206?2?|@O;@9&g zZ6o}QVAVtOGPcJv<2NgLV&87pgUDr0-_C~*cZpI^_fF0uPQ-cRSVi`RG4%^uCK z4vXFQVLooRi}5g_=gY&mnCzQR{r)g+u=(!A&)n%5a!Mf$o8b{l9#_mCza4gunAc)E_x7AH{4e<7 ze~TqSPc7!PMtG%>6LkeXX|Ncw-DTfkDNDrB0S#8s6z2>4iQ(>sT4-D^q2RLjRxYNB z?eZ2Yz9gRSgG4J=ize#NsR+0A7$Va=e9@d3)0~xb5f=m7MZ82iU&5Y~?Y&4r{Xm1c z6f0ve0^7=Y4TbFIyc{btn*O}TO5=(+ja^&q?Ciw(z`BSFh5B=d1FLvw#swDsf+da@ z(U@2lQB=oJe~7EDKVY3Nffe$Ri_t}%M~uRDaVh)CYXPx~N|<(Tz=V8`wiUG^Bv;@O z!VveuaS9A4gtTpeWwuMjksMurfY_!gzOoGv<6Zm#>o`IYlB|nL%sj4&@_cRmT78o} zPcd#-AX^!Rvr$McN~C$zGOX(lSfLoQC1`awjeZhC*B1Q3oali?@~>f; z=N5Bh{_`;x;kkid?6!_Z7hu9V8`ZJSNkeCsq>g#RC(Z10Uwp2t3%Jv60BLjq#<796 zh!~a=pV$VV*p9QoomK?Zj-g0_gjJ*?ia+o!5=|!;6YEk-RgNq0z13(n;{szY@u2(8 z25<*+1mtQY{`1HIfpr1Kyk>HRZ3U|1^=4^WX$;FLytWMEwO|-?BCv`Ay~YN>5FLyq zWF*3{xFXpUxeO%R%Q5Fb4o63Ia+SFb`3RmPIpipQSFya+=_lYsiWdY>;B_fdE8E_% z&IVGI;}mjsR@BLU(_wMD9S_Ia#*eqZMtcz(F0S|cX*eD&bv<0X-EH=O;l#l{&4;hU z%TQL!QkEdj^=7qM^DC% BvVQ;o From 4adbea85b6c3d378a909a3dc62462d904efdc5c4 Mon Sep 17 00:00:00 2001 From: Varun Agrawal Date: Thu, 31 Aug 2023 13:36:27 -0400 Subject: [PATCH 049/113] specify K_{gcm} is K_{gc}(m) --- doc/Hybrid.lyx | 27 +++++++++++++++++++++++++-- doc/Hybrid.pdf | Bin 170632 -> 171197 bytes 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/doc/Hybrid.lyx b/doc/Hybrid.lyx index 3067d5c26..44df81e39 100644 --- a/doc/Hybrid.lyx +++ b/doc/Hybrid.lyx @@ -264,11 +264,34 @@ If we take the log of we get \begin_inset Formula \begin{equation} -\log P(x|y,m)=\log P_{m}(x|y)=K_{gcm}-E_{gcm}(x,y).\label{eq:gm_log} +\log P(x|y,m)=\log P_{m}(x|y)=K_{gc}(m)-E_{gcm}(x,y).\label{eq:gm_log} \end{equation} \end_inset + +\end_layout + +\begin_layout Standard +\noindent +For conciseness, we will write +\begin_inset Formula $K_{gc}(m)$ +\end_inset + + as +\begin_inset Formula $K_{gcm}$ +\end_inset + +. +\end_layout + +\begin_layout Standard +\SpecialChar allowbreak + +\end_layout + +\begin_layout Standard +\noindent The key point here is that \family roman \series medium @@ -307,7 +330,7 @@ GaussianMixture \begin_inset Formula $m$ \end_inset -, and is not dependent on the value of +, and cannot be dependent on the value of \begin_inset Formula $m$ \end_inset diff --git a/doc/Hybrid.pdf b/doc/Hybrid.pdf index 456ce49c4bca1986a443255489ac6ad5053fe838..182573412c3f566b8465ebd384d6875490290502 100644 GIT binary patch delta 10510 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 B3KjqW delta 10015 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 dhwbKW;_B}0YGDcF Date: Fri, 1 Sep 2023 14:12:47 -0400 Subject: [PATCH 050/113] Update CustomFactors.md to more clearly specify types required --- python/CustomFactors.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/python/CustomFactors.md b/python/CustomFactors.md index a6ffa2f36..079bcaa85 100644 --- a/python/CustomFactors.md +++ b/python/CustomFactors.md @@ -11,7 +11,7 @@ import gtsam import numpy as np from typing import List -def error_func(this: gtsam.CustomFactor, v: gtsam.Values, H: List[np.ndarray]): +def error_func(this: gtsam.CustomFactor, v: gtsam.Values, H: List[np.ndarray]) -> np.ndarray: ... ``` @@ -21,7 +21,7 @@ def error_func(this: gtsam.CustomFactor, v: gtsam.Values, H: List[np.ndarray]): If `H` is `None`, it means the current factor evaluation does not need Jacobians. For example, the `error` method on a factor does not need Jacobians, so we don't evaluate them to save CPU. If `H` is not `None`, -each entry of `H` can be assigned a `numpy` array, as the Jacobian for the corresponding variable. +each entry of `H` can be assigned a (2D) `numpy` array, as the Jacobian for the corresponding variable. After defining `error_func`, one can create a `CustomFactor` just like any other factor in GTSAM: @@ -42,7 +42,7 @@ from typing import List expected = Pose2(2, 2, np.pi / 2) -def error_func(this: CustomFactor, v: gtsam.Values, H: List[np.ndarray]): +def error_func(this: CustomFactor, v: gtsam.Values, H: List[np.ndarray]) -> np.ndarray: """ Error function that mimics a BetweenFactor :param this: reference to the current CustomFactor being evaluated From 8ee9f205378b1082ab4201b5723e3848b0f155a3 Mon Sep 17 00:00:00 2001 From: Clark Taylor Date: Fri, 1 Sep 2023 14:15:39 -0400 Subject: [PATCH 051/113] Update CustomFactorExample.py to correctly document return types --- python/gtsam/examples/CustomFactorExample.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/python/gtsam/examples/CustomFactorExample.py b/python/gtsam/examples/CustomFactorExample.py index c7ae8ad21..1a8d1a61d 100644 --- a/python/gtsam/examples/CustomFactorExample.py +++ b/python/gtsam/examples/CustomFactorExample.py @@ -30,7 +30,7 @@ def simulate_car() -> List[float]: def error_gps(measurement: np.ndarray, this: gtsam.CustomFactor, values: gtsam.Values, - jacobians: Optional[List[np.ndarray]]) -> float: + jacobians: Optional[List[np.ndarray]]) -> np.ndarray: """GPS Factor error function :param measurement: GPS measurement, to be filled with `partial` :param this: gtsam.CustomFactor handle @@ -49,7 +49,7 @@ def error_gps(measurement: np.ndarray, this: gtsam.CustomFactor, def error_odom(measurement: np.ndarray, this: gtsam.CustomFactor, values: gtsam.Values, - jacobians: Optional[List[np.ndarray]]) -> float: + jacobians: Optional[List[np.ndarray]]) -> np.ndarray: """Odometry Factor error function :param measurement: Odometry measurement, to be filled with `partial` :param this: gtsam.CustomFactor handle @@ -70,7 +70,7 @@ def error_odom(measurement: np.ndarray, this: gtsam.CustomFactor, def error_lm(measurement: np.ndarray, this: gtsam.CustomFactor, values: gtsam.Values, - jacobians: Optional[List[np.ndarray]]) -> float: + jacobians: Optional[List[np.ndarray]]) -> np.ndarray: """Landmark Factor error function :param measurement: Landmark measurement, to be filled with `partial` :param this: gtsam.CustomFactor handle From 8611aec3fb31698483f5afa5e2519de2ce61ddd0 Mon Sep 17 00:00:00 2001 From: Clark Taylor Date: Fri, 1 Sep 2023 14:17:04 -0400 Subject: [PATCH 052/113] Update CustomFactors.md to document return type. --- python/CustomFactors.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/python/CustomFactors.md b/python/CustomFactors.md index 079bcaa85..39a840e34 100644 --- a/python/CustomFactors.md +++ b/python/CustomFactors.md @@ -17,7 +17,8 @@ def error_func(this: gtsam.CustomFactor, v: gtsam.Values, H: List[np.ndarray]) - `this` is a reference to the `CustomFactor` object. This is required because one can reuse the same `error_func` for multiple factors. `v` is a reference to the current set of values, and `H` is a list of -**references** to the list of required Jacobians (see the corresponding C++ documentation). +**references** to the list of required Jacobians (see the corresponding C++ documentation). Note that +the error returned must be a 1D numpy array. If `H` is `None`, it means the current factor evaluation does not need Jacobians. For example, the `error` method on a factor does not need Jacobians, so we don't evaluate them to save CPU. If `H` is not `None`, From 039f5cf7110daefe0299688b44ee7a9b604f06ca Mon Sep 17 00:00:00 2001 From: Clark Taylor Date: Fri, 1 Sep 2023 14:18:45 -0400 Subject: [PATCH 053/113] Update CustomFactorExample.py with comments to explain typing --- python/gtsam/examples/CustomFactorExample.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/python/gtsam/examples/CustomFactorExample.py b/python/gtsam/examples/CustomFactorExample.py index 1a8d1a61d..a78562ecb 100644 --- a/python/gtsam/examples/CustomFactorExample.py +++ b/python/gtsam/examples/CustomFactorExample.py @@ -15,7 +15,7 @@ from typing import List, Optional import gtsam import numpy as np -I = np.eye(1) +I = np.eye(1) # Creates a 1-element, 2D array def simulate_car() -> List[float]: @@ -44,7 +44,7 @@ def error_gps(measurement: np.ndarray, this: gtsam.CustomFactor, if jacobians is not None: jacobians[0] = I - return error + return error # with input types this is a 1D np.ndarray def error_odom(measurement: np.ndarray, this: gtsam.CustomFactor, From e57046bca062bcc4c48cb6c8cafbdd588e40fe91 Mon Sep 17 00:00:00 2001 From: Frank Dellaert Date: Sun, 3 Sep 2023 19:47:16 -0700 Subject: [PATCH 054/113] Update README.md with release 4.2 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1ccde9738..40b84dff8 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ **As of January 2023, the `develop` branch is officially in "Pre 4.3" mode. We envision several API-breaking changes as we switch to C++17 and away from boost.** -In addition, features deprecated in 4.2 will be removed. Please use the last [4.2a8 release](https://github.com/borglab/gtsam/releases/tag/4.2a8) if you need those features. However, most are easily converted and can be tracked down (in 4.2) by disabling the cmake flag `GTSAM_ALLOW_DEPRECATED_SINCE_V42`. +In addition, features deprecated in 4.2 will be removed. Please use the stable [4.2 release](https://github.com/borglab/gtsam/releases/tag/4.2) if you need those features. However, most are easily converted and can be tracked down (in 4.2) by disabling the cmake flag `GTSAM_ALLOW_DEPRECATED_SINCE_V42`. ## What is GTSAM? From ac3f3dbd60f50a67cfae1a7d76587aa01e4c75c6 Mon Sep 17 00:00:00 2001 From: Varun Agrawal Date: Mon, 4 Sep 2023 22:25:34 -0400 Subject: [PATCH 055/113] clean up GTSAM version setting --- CMakeLists.txt | 8 +++----- gtsam/CMakeLists.txt | 2 +- gtsam_unstable/CMakeLists.txt | 2 +- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 8b56ea9df..309cc7c4e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,18 +13,16 @@ set (GTSAM_VERSION_PATCH 0) set (GTSAM_PRERELEASE_VERSION "a0") math (EXPR GTSAM_VERSION_NUMERIC "10000 * ${GTSAM_VERSION_MAJOR} + 100 * ${GTSAM_VERSION_MINOR} + ${GTSAM_VERSION_PATCH}") -if (${GTSAM_VERSION_PATCH} EQUAL 0) - set (GTSAM_VERSION_STRING "${GTSAM_VERSION_MAJOR}.${GTSAM_VERSION_MINOR}${GTSAM_PRERELEASE_VERSION}") +if ("${GTSAM_PRERELEASE_VERSION}" STREQUAL "") + set (GTSAM_VERSION_STRING "${GTSAM_VERSION_MAJOR}.${GTSAM_VERSION_MINOR}.${GTSAM_VERSION_PATCH}") else() - set (GTSAM_VERSION_STRING "${GTSAM_VERSION_MAJOR}.${GTSAM_VERSION_MINOR}.${GTSAM_VERSION_PATCH}${GTSAM_PRERELEASE_VERSION}") + set (GTSAM_VERSION_STRING "${GTSAM_VERSION_MAJOR}.${GTSAM_VERSION_MINOR}${GTSAM_PRERELEASE_VERSION}") endif() project(GTSAM LANGUAGES CXX C VERSION "${GTSAM_VERSION_MAJOR}.${GTSAM_VERSION_MINOR}.${GTSAM_VERSION_PATCH}") -message(STATUS "GTSAM Version: ${GTSAM_VERSION_STRING}") - set (CMAKE_PROJECT_VERSION_MAJOR ${GTSAM_VERSION_MAJOR}) set (CMAKE_PROJECT_VERSION_MINOR ${GTSAM_VERSION_MINOR}) set (CMAKE_PROJECT_VERSION_PATCH ${GTSAM_VERSION_PATCH}) diff --git a/gtsam/CMakeLists.txt b/gtsam/CMakeLists.txt index efd0e9dc2..555e077b8 100644 --- a/gtsam/CMakeLists.txt +++ b/gtsam/CMakeLists.txt @@ -112,7 +112,7 @@ if(GTSAM_SUPPORT_NESTED_DISSECTION) endif() # Versions -set(gtsam_version ${GTSAM_VERSION_MAJOR}.${GTSAM_VERSION_MINOR}.${GTSAM_VERSION_PATCH}) +set(gtsam_version ${GTSAM_VERSION_STRING}) set(gtsam_soversion ${GTSAM_VERSION_MAJOR}) message(STATUS "GTSAM Version: ${gtsam_version}") message(STATUS "Install prefix: ${CMAKE_INSTALL_PREFIX}") diff --git a/gtsam_unstable/CMakeLists.txt b/gtsam_unstable/CMakeLists.txt index 30f096772..1f7b4a7c7 100644 --- a/gtsam_unstable/CMakeLists.txt +++ b/gtsam_unstable/CMakeLists.txt @@ -82,7 +82,7 @@ if(GTSAM_SUPPORT_NESTED_DISSECTION) # Only build partition if metis is built endif(GTSAM_SUPPORT_NESTED_DISSECTION) # Versions - same as core gtsam library -set(gtsam_unstable_version ${GTSAM_VERSION_MAJOR}.${GTSAM_VERSION_MINOR}.${GTSAM_VERSION_PATCH}) +set(gtsam_unstable_version ${GTSAM_VERSION_STRING}) set(gtsam_unstable_soversion ${GTSAM_VERSION_MAJOR}) message(STATUS "GTSAM_UNSTABLE Version: ${gtsam_unstable_version}") message(STATUS "Install prefix: ${CMAKE_INSTALL_PREFIX}") From d7be9b92267c701635bd6544d34f160ba62dbd15 Mon Sep 17 00:00:00 2001 From: Varun Agrawal Date: Tue, 5 Sep 2023 12:04:33 -0400 Subject: [PATCH 056/113] Squashed 'wrap/' changes from 2f136936d..390cb8092 390cb8092 Merge pull request #160 from borglab/upgrade-pybind11 c48bfa418 upgrade pybind11 to v2.11.1 c941bdd48 update gitignore git-subtree-dir: wrap git-subtree-split: 390cb80923c409e20a2467969dc44f2677fc265a --- .gitignore | 2 + pybind11/.clang-tidy | 2 + pybind11/.codespell-ignore-lines | 24 + pybind11/.github/CONTRIBUTING.md | 4 +- .../.github/ISSUE_TEMPLATE/bug-report.yml | 22 +- pybind11/.github/workflows/ci.yml | 291 ++++++- pybind11/.github/workflows/configure.yml | 26 +- pybind11/.github/workflows/format.yml | 9 +- pybind11/.github/workflows/labeler.yml | 11 +- pybind11/.github/workflows/pip.yml | 12 +- pybind11/.github/workflows/upstream.yml | 78 +- pybind11/.gitignore | 1 + pybind11/.pre-commit-config.yaml | 157 ++-- pybind11/CMakeLists.txt | 33 +- pybind11/MANIFEST.in | 3 +- pybind11/README.rst | 2 +- pybind11/SECURITY.md | 13 + pybind11/docs/advanced/cast/custom.rst | 4 +- pybind11/docs/advanced/cast/stl.rst | 6 +- pybind11/docs/advanced/cast/strings.rst | 10 +- pybind11/docs/advanced/classes.rst | 4 +- pybind11/docs/advanced/embedding.rst | 2 +- pybind11/docs/advanced/exceptions.rst | 9 +- pybind11/docs/advanced/misc.rst | 77 +- pybind11/docs/advanced/pycpp/numpy.rst | 4 +- pybind11/docs/advanced/smart_ptrs.rst | 2 +- pybind11/docs/changelog.rst | 358 ++++++++ pybind11/docs/classes.rst | 14 + pybind11/docs/compiling.rst | 9 +- pybind11/docs/conf.py | 3 +- pybind11/docs/faq.rst | 3 +- pybind11/docs/release.rst | 10 +- pybind11/docs/upgrade.rst | 14 + pybind11/include/pybind11/attr.h | 18 +- pybind11/include/pybind11/buffer_info.h | 17 +- pybind11/include/pybind11/cast.h | 99 ++- pybind11/include/pybind11/detail/class.h | 59 +- pybind11/include/pybind11/detail/common.h | 178 +++- pybind11/include/pybind11/detail/descr.h | 13 + pybind11/include/pybind11/detail/init.h | 40 +- pybind11/include/pybind11/detail/internals.h | 124 ++- .../pybind11/detail/type_caster_base.h | 219 ++++- pybind11/include/pybind11/eigen.h | 698 +-------------- pybind11/include/pybind11/eigen/common.h | 9 + pybind11/include/pybind11/eigen/matrix.h | 714 ++++++++++++++++ pybind11/include/pybind11/eigen/tensor.h | 516 +++++++++++ pybind11/include/pybind11/embed.h | 131 ++- pybind11/include/pybind11/functional.h | 13 +- pybind11/include/pybind11/gil.h | 63 +- pybind11/include/pybind11/numpy.h | 48 +- pybind11/include/pybind11/operators.h | 1 + pybind11/include/pybind11/options.h | 16 + pybind11/include/pybind11/pybind11.h | 188 ++-- pybind11/include/pybind11/pytypes.h | 337 ++++++-- pybind11/include/pybind11/stl.h | 50 +- pybind11/include/pybind11/stl_bind.h | 176 ++-- .../pybind11/type_caster_pyobject_ptr.h | 61 ++ pybind11/noxfile.py | 18 +- pybind11/pybind11/__init__.py | 5 +- pybind11/pybind11/__main__.py | 17 +- pybind11/pybind11/_version.py | 2 +- pybind11/pybind11/commands.py | 14 +- pybind11/pybind11/setup_helpers.py | 22 +- pybind11/pyproject.toml | 49 +- pybind11/setup.cfg | 9 +- pybind11/setup.py | 3 +- pybind11/tests/CMakeLists.txt | 48 +- pybind11/tests/conftest.py | 76 +- pybind11/tests/constructor_stats.h | 2 +- pybind11/tests/cross_module_gil_utils.cpp | 67 +- .../tests/eigen_tensor_avoid_stl_array.cpp | 14 + pybind11/tests/env.py | 3 +- .../tests/extra_python_package/test_files.py | 137 +-- pybind11/tests/pybind11_tests.cpp | 6 + pybind11/tests/requirements.txt | 2 +- pybind11/tests/test_async.py | 4 +- pybind11/tests/test_buffers.cpp | 35 + pybind11/tests/test_buffers.py | 60 +- pybind11/tests/test_builtin_casters.cpp | 18 +- pybind11/tests/test_builtin_casters.py | 10 +- pybind11/tests/test_callbacks.cpp | 37 + pybind11/tests/test_callbacks.py | 27 +- pybind11/tests/test_chrono.py | 4 - pybind11/tests/test_class.cpp | 48 +- pybind11/tests/test_class.py | 24 +- .../tests/test_cmake_build/CMakeLists.txt | 3 - .../installed_embed/CMakeLists.txt | 8 +- .../installed_function/CMakeLists.txt | 8 +- .../installed_target/CMakeLists.txt | 8 +- .../subdirectory_embed/CMakeLists.txt | 8 +- .../subdirectory_function/CMakeLists.txt | 8 +- .../subdirectory_target/CMakeLists.txt | 8 +- pybind11/tests/test_const_name.py | 4 +- .../tests/test_constants_and_functions.cpp | 37 +- .../tests/test_constants_and_functions.py | 4 + pybind11/tests/test_copy_move.cpp | 238 ++++++ pybind11/tests/test_custom_type_casters.cpp | 14 +- pybind11/tests/test_custom_type_casters.py | 6 +- pybind11/tests/test_custom_type_setup.py | 2 +- pybind11/tests/test_docstring_options.cpp | 53 ++ pybind11/tests/test_docstring_options.py | 23 + pybind11/tests/test_eigen_matrix.cpp | 428 ++++++++++ pybind11/tests/test_eigen_matrix.py | 807 ++++++++++++++++++ pybind11/tests/test_eigen_tensor.cpp | 18 + pybind11/tests/test_eigen_tensor.inl | 333 ++++++++ pybind11/tests/test_eigen_tensor.py | 288 +++++++ pybind11/tests/test_embed/catch.cpp | 22 +- .../tests/test_embed/test_interpreter.cpp | 123 ++- pybind11/tests/test_enum.py | 2 + pybind11/tests/test_exceptions.cpp | 20 +- pybind11/tests/test_exceptions.py | 87 +- pybind11/tests/test_factory_constructors.py | 2 +- pybind11/tests/test_gil_scoped.cpp | 107 ++- pybind11/tests/test_gil_scoped.py | 225 ++++- pybind11/tests/test_iostream.py | 78 +- pybind11/tests/test_kwargs_and_defaults.cpp | 10 +- pybind11/tests/test_kwargs_and_defaults.py | 31 +- pybind11/tests/test_local_bindings.py | 3 +- .../tests/test_methods_and_attributes.cpp | 34 + pybind11/tests/test_methods_and_attributes.py | 18 +- pybind11/tests/test_modules.py | 19 +- pybind11/tests/test_numpy_array.cpp | 28 + pybind11/tests/test_numpy_array.py | 95 ++- pybind11/tests/test_numpy_dtypes.py | 28 +- pybind11/tests/test_numpy_vectorize.py | 2 +- pybind11/tests/test_operator_overloading.cpp | 23 +- pybind11/tests/test_operator_overloading.py | 1 - pybind11/tests/test_pytypes.cpp | 66 +- pybind11/tests/test_pytypes.py | 187 +++- .../tests/test_sequences_and_iterators.cpp | 19 + .../tests/test_sequences_and_iterators.py | 13 +- pybind11/tests/test_smart_ptr.cpp | 4 +- pybind11/tests/test_stl.cpp | 22 +- pybind11/tests/test_stl.py | 16 +- pybind11/tests/test_stl_binders.cpp | 44 + pybind11/tests/test_stl_binders.py | 56 +- pybind11/tests/test_tagbased_polymorphic.cpp | 4 +- .../tests/test_type_caster_pyobject_ptr.cpp | 130 +++ .../tests/test_type_caster_pyobject_ptr.py | 104 +++ pybind11/tests/test_unnamed_namespace_a.cpp | 38 + pybind11/tests/test_unnamed_namespace_a.py | 34 + pybind11/tests/test_unnamed_namespace_b.cpp | 13 + pybind11/tests/test_unnamed_namespace_b.py | 5 + .../tests/test_vector_unique_ptr_member.cpp | 54 ++ .../tests/test_vector_unique_ptr_member.py | 14 + pybind11/tests/test_virtual_functions.cpp | 3 +- pybind11/tests/test_virtual_functions.py | 7 +- pybind11/tools/FindCatch.cmake | 8 +- pybind11/tools/FindPythonLibsNew.cmake | 12 +- pybind11/tools/JoinPaths.cmake | 23 + .../codespell_ignore_lines_from_errors.py | 39 + pybind11/tools/make_changelog.py | 9 +- pybind11/tools/pybind11.pc.in | 7 + pybind11/tools/pybind11Common.cmake | 36 +- pybind11/tools/pybind11Config.cmake.in | 4 +- pybind11/tools/pybind11NewTools.cmake | 6 +- pybind11/tools/pybind11Tools.cmake | 16 +- pybind11/tools/setup_global.py.in | 6 +- pybind11/tools/setup_main.py.in | 4 + 159 files changed, 8117 insertions(+), 1918 deletions(-) create mode 100644 pybind11/.codespell-ignore-lines create mode 100644 pybind11/SECURITY.md create mode 100644 pybind11/include/pybind11/eigen/common.h create mode 100644 pybind11/include/pybind11/eigen/matrix.h create mode 100644 pybind11/include/pybind11/eigen/tensor.h create mode 100644 pybind11/include/pybind11/type_caster_pyobject_ptr.h create mode 100644 pybind11/tests/eigen_tensor_avoid_stl_array.cpp create mode 100644 pybind11/tests/test_eigen_matrix.cpp create mode 100644 pybind11/tests/test_eigen_matrix.py create mode 100644 pybind11/tests/test_eigen_tensor.cpp create mode 100644 pybind11/tests/test_eigen_tensor.inl create mode 100644 pybind11/tests/test_eigen_tensor.py create mode 100644 pybind11/tests/test_type_caster_pyobject_ptr.cpp create mode 100644 pybind11/tests/test_type_caster_pyobject_ptr.py create mode 100644 pybind11/tests/test_unnamed_namespace_a.cpp create mode 100644 pybind11/tests/test_unnamed_namespace_a.py create mode 100644 pybind11/tests/test_unnamed_namespace_b.cpp create mode 100644 pybind11/tests/test_unnamed_namespace_b.py create mode 100644 pybind11/tests/test_vector_unique_ptr_member.cpp create mode 100644 pybind11/tests/test_vector_unique_ptr_member.py create mode 100644 pybind11/tools/JoinPaths.cmake create mode 100644 pybind11/tools/codespell_ignore_lines_from_errors.py create mode 100644 pybind11/tools/pybind11.pc.in diff --git a/.gitignore b/.gitignore index 9f79deafa..de16b118d 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,8 @@ __pycache__/ *dist* *.egg-info +**/.DS_Store + # Files related to code coverage stats **/.coverage diff --git a/pybind11/.clang-tidy b/pybind11/.clang-tidy index d945a4a27..23018386c 100644 --- a/pybind11/.clang-tidy +++ b/pybind11/.clang-tidy @@ -63,6 +63,8 @@ Checks: | -bugprone-unused-raii, CheckOptions: +- key: modernize-use-equals-default.IgnoreMacros + value: false - key: performance-for-range-copy.WarnOnAllAutoCopies value: true - key: performance-inefficient-string-concatenation.StrictMode diff --git a/pybind11/.codespell-ignore-lines b/pybind11/.codespell-ignore-lines new file mode 100644 index 000000000..2a01d63eb --- /dev/null +++ b/pybind11/.codespell-ignore-lines @@ -0,0 +1,24 @@ +template + template + auto &this_ = static_cast(*this); + if (load_impl(temp, false)) { + ssize_t nd = 0; + auto trivial = broadcast(buffers, nd, shape); + auto ndim = (size_t) nd; + int nd; + ssize_t ndim() const { return detail::array_proxy(m_ptr)->nd; } + using op = op_impl; +template + template + class_ &def(const detail::op_ &op, const Extra &...extra) { + class_ &def_cast(const detail::op_ &op, const Extra &...extra) { +@pytest.mark.parametrize("access", ["ro", "rw", "static_ro", "static_rw"]) +struct IntStruct { + explicit IntStruct(int v) : value(v){}; + ~IntStruct() { value = -value; } + IntStruct(const IntStruct &) = default; + IntStruct &operator=(const IntStruct &) = default; + py::class_(m, "IntStruct").def(py::init([](const int i) { return IntStruct(i); })); + py::implicitly_convertible(); + m.def("test", [](int expected, const IntStruct &in) { + [](int expected, const IntStruct &in) { diff --git a/pybind11/.github/CONTRIBUTING.md b/pybind11/.github/CONTRIBUTING.md index 00b1fea4c..ad7974395 100644 --- a/pybind11/.github/CONTRIBUTING.md +++ b/pybind11/.github/CONTRIBUTING.md @@ -235,8 +235,8 @@ directory inside your pybind11 git clone. Files will be modified in place, so you can use git to monitor the changes. ```bash -docker run --rm -v $PWD:/mounted_pybind11 -it silkeh/clang:13 -apt-get update && apt-get install -y python3-dev python3-pytest +docker run --rm -v $PWD:/mounted_pybind11 -it silkeh/clang:15-bullseye +apt-get update && apt-get install -y git python3-dev python3-pytest cmake -S /mounted_pybind11/ -B build -DCMAKE_CXX_CLANG_TIDY="$(which clang-tidy);--use-color" -DDOWNLOAD_EIGEN=ON -DDOWNLOAD_CATCH=ON -DCMAKE_CXX_STANDARD=17 cmake --build build -j 2 ``` diff --git a/pybind11/.github/ISSUE_TEMPLATE/bug-report.yml b/pybind11/.github/ISSUE_TEMPLATE/bug-report.yml index bd6a9a8e2..4f1e78f33 100644 --- a/pybind11/.github/ISSUE_TEMPLATE/bug-report.yml +++ b/pybind11/.github/ISSUE_TEMPLATE/bug-report.yml @@ -6,7 +6,8 @@ body: - type: markdown attributes: value: | - Maintainers will only make a best effort to triage PRs. Please do your best to make the issue as easy to act on as possible, and only open if clearly a problem with pybind11 (ask first if unsure). + Please do your best to make the issue as easy to act on as possible, and only submit here if there is clearly a problem with pybind11 (ask first if unsure). **Note that a reproducer in a PR is much more likely to get immediate attention.** + - type: checkboxes id: steps attributes: @@ -20,6 +21,13 @@ body: - label: Consider asking first in the [Gitter chat room](https://gitter.im/pybind/Lobby) or in a [Discussion](https:/pybind/pybind11/discussions/new). required: false + - type: input + id: version + attributes: + label: What version (or hash if on master) of pybind11 are you using? + validations: + required: true + - type: textarea id: description attributes: @@ -40,6 +48,14 @@ body: The code should be minimal, have no external dependencies, isolate the function(s) that cause breakage. Submit matched and complete C++ and Python snippets that can be easily compiled and run to diagnose the - issue. If possible, make a PR with a new, failing test to give us a - starting point to work on! + issue. — Note that a reproducer in a PR is much more likely to get + immediate attention: failing tests in the pybind11 CI are the best + starting point for working out fixes. render: text + + - type: input + id: regression + attributes: + label: Is this a regression? Put the last known working version here if it is. + description: Put the last known working version here if this is a regression. + value: Not a regression diff --git a/pybind11/.github/workflows/ci.yml b/pybind11/.github/workflows/ci.yml index 412282a4f..48f7c5e93 100644 --- a/pybind11/.github/workflows/ci.yml +++ b/pybind11/.github/workflows/ci.yml @@ -9,14 +9,19 @@ on: - stable - v* +permissions: read-all + concurrency: group: test-${{ github.ref }} cancel-in-progress: true env: + PIP_BREAK_SYSTEM_PACKAGES: 1 PIP_ONLY_BINARY: numpy FORCE_COLOR: 3 PYTEST_TIMEOUT: 300 + # For cmake: + VERBOSE: 1 jobs: # This is the "main" test suite, which tests a large number of different @@ -25,15 +30,16 @@ jobs: strategy: fail-fast: false matrix: - runs-on: [ubuntu-latest, windows-2022, macos-latest] + runs-on: [ubuntu-20.04, windows-2022, macos-latest] python: - '3.6' - '3.9' - '3.10' - - '3.11-dev' - - 'pypy-3.7' + - '3.11' + - '3.12' - 'pypy-3.8' - 'pypy-3.9' + - 'pypy-3.10' # Items in here will either be added to the build matrix (if not # present), or add new keys to an existing matrix element if all the @@ -42,12 +48,12 @@ jobs: # We support an optional key: args, for cmake args include: # Just add a key - - runs-on: ubuntu-latest + - runs-on: ubuntu-20.04 python: '3.6' args: > -DPYBIND11_FINDPYTHON=ON -DCMAKE_CXX_FLAGS="-D_=1" - - runs-on: ubuntu-latest + - runs-on: ubuntu-20.04 python: 'pypy-3.8' args: > -DPYBIND11_FINDPYTHON=ON @@ -69,6 +75,7 @@ jobs: uses: actions/setup-python@v4 with: python-version: ${{ matrix.python }} + allow-prereleases: true - name: Setup Boost (Linux) # Can't use boost + define _ @@ -80,7 +87,7 @@ jobs: run: brew install boost - name: Update CMake - uses: jwlawson/actions-setup-cmake@v1.12 + uses: jwlawson/actions-setup-cmake@v1.14 - name: Cache wheels if: runner.os == 'macOS' @@ -102,10 +109,12 @@ jobs: run: python -m pip install pytest-github-actions-annotate-failures # First build - C++11 mode and inplace + # More-or-less randomly adding -DPYBIND11_SIMPLE_GIL_MANAGEMENT=ON here. - name: Configure C++11 ${{ matrix.args }} run: > cmake -S . -B . -DPYBIND11_WERROR=ON + -DPYBIND11_SIMPLE_GIL_MANAGEMENT=ON -DDOWNLOAD_CATCH=ON -DDOWNLOAD_EIGEN=ON -DCMAKE_CXX_STANDARD=11 @@ -119,7 +128,7 @@ jobs: - name: C++11 tests # TODO: Figure out how to load the DLL on Python 3.8+ - if: "!(runner.os == 'Windows' && (matrix.python == 3.8 || matrix.python == 3.9 || matrix.python == '3.10' || matrix.python == '3.11-dev' || matrix.python == 'pypy-3.8'))" + if: "!(runner.os == 'Windows' && (matrix.python == 3.8 || matrix.python == 3.9 || matrix.python == '3.10' || matrix.python == '3.11' || matrix.python == 'pypy-3.8'))" run: cmake --build . --target cpptest -j 2 - name: Interface test C++11 @@ -129,10 +138,12 @@ jobs: run: git clean -fdx # Second build - C++17 mode and in a build directory + # More-or-less randomly adding -DPYBIND11_SIMPLE_GIL_MANAGEMENT=OFF here. - name: Configure C++17 run: > cmake -S . -B build2 -DPYBIND11_WERROR=ON + -DPYBIND11_SIMPLE_GIL_MANAGEMENT=OFF -DDOWNLOAD_CATCH=ON -DDOWNLOAD_EIGEN=ON -DCMAKE_CXX_STANDARD=17 @@ -146,7 +157,7 @@ jobs: - name: C++ tests # TODO: Figure out how to load the DLL on Python 3.8+ - if: "!(runner.os == 'Windows' && (matrix.python == 3.8 || matrix.python == 3.9 || matrix.python == '3.10' || matrix.python == '3.11-dev' || matrix.python == 'pypy-3.8'))" + if: "!(runner.os == 'Windows' && (matrix.python == 3.8 || matrix.python == 3.9 || matrix.python == '3.10' || matrix.python == '3.11' || matrix.python == 'pypy-3.8'))" run: cmake --build build2 --target cpptest # Third build - C++17 mode with unstable ABI @@ -158,7 +169,6 @@ jobs: -DDOWNLOAD_EIGEN=ON -DCMAKE_CXX_STANDARD=17 -DPYBIND11_INTERNALS_VERSION=10000000 - "-DPYBIND11_TEST_OVERRIDE=test_call_policies.cpp;test_gil_scoped.cpp;test_thread.cpp" ${{ matrix.args }} - name: Build (unstable ABI) @@ -173,7 +183,9 @@ jobs: # This makes sure the setup_helpers module can build packages using # setuptools - name: Setuptools helpers test - run: pytest tests/extra_setuptools + run: | + pip install setuptools + pytest tests/extra_setuptools if: "!(matrix.runs-on == 'windows-2022')" @@ -186,23 +198,23 @@ jobs: - python-version: "3.9" python-debug: true valgrind: true - - python-version: "3.11-dev" + - python-version: "3.11" python-debug: false name: "🐍 ${{ matrix.python-version }}${{ matrix.python-debug && '-dbg' || '' }} (deadsnakes)${{ matrix.valgrind && ' • Valgrind' || '' }} • x64" - runs-on: ubuntu-latest + runs-on: ubuntu-20.04 steps: - uses: actions/checkout@v3 - name: Setup Python ${{ matrix.python-version }} (deadsnakes) - uses: deadsnakes/action@v2.1.1 + uses: deadsnakes/action@v3.0.1 with: python-version: ${{ matrix.python-version }} debug: ${{ matrix.python-debug }} - name: Update CMake - uses: jwlawson/actions-setup-cmake@v1.12 + uses: jwlawson/actions-setup-cmake@v1.14 - name: Valgrind cache if: matrix.valgrind @@ -235,8 +247,6 @@ jobs: python -m pip install -r tests/requirements.txt - name: Configure - env: - SETUPTOOLS_USE_DISTUTILS: stdlib run: > cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug @@ -274,18 +284,27 @@ jobs: - dev std: - 11 + container_suffix: + - "" include: - clang: 5 std: 14 - - clang: 10 - std: 20 - clang: 10 std: 17 + - clang: 11 + std: 20 + - clang: 12 + std: 20 + - clang: 13 + std: 20 - clang: 14 std: 20 + - clang: 15 + std: 20 + container_suffix: "-bullseye" name: "🐍 3 • Clang ${{ matrix.clang }} • C++${{ matrix.std }} • x64" - container: "silkeh/clang:${{ matrix.clang }}" + container: "silkeh/clang:${{ matrix.clang }}${{ matrix.container_suffix }}" steps: - uses: actions/checkout@v3 @@ -318,8 +337,8 @@ jobs: # Testing NVCC; forces sources to behave like .cu files cuda: runs-on: ubuntu-latest - name: "🐍 3.8 • CUDA 11.2 • Ubuntu 20.04" - container: nvidia/cuda:11.2.2-devel-ubuntu20.04 + name: "🐍 3.10 • CUDA 12.2 • Ubuntu 22.04" + container: nvidia/cuda:12.2.0-devel-ubuntu22.04 steps: - uses: actions/checkout@v3 @@ -384,8 +403,9 @@ jobs: # Testing on CentOS 7 + PGI compilers, which seems to require more workarounds centos-nvhpc7: + if: ${{ false }} # JOB DISABLED (NEEDS WORK): https://github.com/pybind/pybind11/issues/4690 runs-on: ubuntu-latest - name: "🐍 3 • CentOS7 / PGI 22.3 • x64" + name: "🐍 3 • CentOS7 / PGI 22.9 • x64" container: centos:7 steps: @@ -395,7 +415,7 @@ jobs: run: yum update -y && yum install -y epel-release && yum install -y git python3-devel make environment-modules cmake3 yum-utils - name: Install NVidia HPC SDK - run: yum-config-manager --add-repo https://developer.download.nvidia.com/hpc-sdk/rhel/nvhpc.repo && yum -y install nvhpc-22.3 + run: yum-config-manager --add-repo https://developer.download.nvidia.com/hpc-sdk/rhel/nvhpc.repo && yum -y install nvhpc-22.9 # On CentOS 7, we have to filter a few tests (compiler internal error) # and allow deeper template recursion (not needed on CentOS 8 with a newer @@ -405,12 +425,12 @@ jobs: shell: bash run: | source /etc/profile.d/modules.sh - module load /opt/nvidia/hpc_sdk/modulefiles/nvhpc/22.3 + module load /opt/nvidia/hpc_sdk/modulefiles/nvhpc/22.9 cmake3 -S . -B build -DDOWNLOAD_CATCH=ON \ -DCMAKE_CXX_STANDARD=11 \ -DPYTHON_EXECUTABLE=$(python3 -c "import sys; print(sys.executable)") \ -DCMAKE_CXX_FLAGS="-Wc,--pending_instantiations=0" \ - -DPYBIND11_TEST_FILTER="test_smart_ptr.cpp;test_virtual_functions.cpp" + -DPYBIND11_TEST_FILTER="test_smart_ptr.cpp" # Building before installing Pip should produce a warning but not an error - name: Build @@ -437,14 +457,14 @@ jobs: strategy: fail-fast: false matrix: - gcc: - - 7 - - latest - std: - - 11 include: - - gcc: 10 - std: 20 + - { gcc: 7, std: 11 } + - { gcc: 7, std: 17 } + - { gcc: 8, std: 14 } + - { gcc: 8, std: 17 } + - { gcc: 10, std: 17 } + - { gcc: 11, std: 20 } + - { gcc: 12, std: 20 } name: "🐍 3 • GCC ${{ matrix.gcc }} • C++${{ matrix.std }}• x64" container: "gcc:${{ matrix.gcc }}" @@ -459,7 +479,7 @@ jobs: run: python3 -m pip install --upgrade pip - name: Update CMake - uses: jwlawson/actions-setup-cmake@v1.12 + uses: jwlawson/actions-setup-cmake@v1.14 - name: Configure shell: bash @@ -482,6 +502,24 @@ jobs: - name: Interface test run: cmake --build build --target test_cmake_build + - name: Configure - Exercise cmake -DPYBIND11_TEST_OVERRIDE + if: matrix.gcc == '12' + shell: bash + run: > + cmake -S . -B build_partial + -DPYBIND11_WERROR=ON + -DDOWNLOAD_CATCH=ON + -DCMAKE_CXX_STANDARD=${{ matrix.std }} + -DPYTHON_EXECUTABLE=$(python3 -c "import sys; print(sys.executable)") + "-DPYBIND11_TEST_OVERRIDE=test_call_policies.cpp;test_gil_scoped.cpp;test_thread.cpp" + + - name: Build - Exercise cmake -DPYBIND11_TEST_OVERRIDE + if: matrix.gcc == '12' + run: cmake --build build_partial -j 2 + + - name: Python tests - Exercise cmake -DPYBIND11_TEST_OVERRIDE + if: matrix.gcc == '12' + run: cmake --build build_partial --target pytest # Testing on ICC using the oneAPI apt repo icc: @@ -731,6 +769,9 @@ jobs: args: -DCMAKE_CXX_STANDARD=20 - python: 3.8 args: -DCMAKE_CXX_STANDARD=17 + - python: 3.7 + args: -DCMAKE_CXX_STANDARD=14 + name: "🐍 ${{ matrix.python }} • MSVC 2019 • x86 ${{ matrix.args }}" runs-on: windows-2019 @@ -745,10 +786,10 @@ jobs: architecture: x86 - name: Update CMake - uses: jwlawson/actions-setup-cmake@v1.12 + uses: jwlawson/actions-setup-cmake@v1.14 - name: Prepare MSVC - uses: ilammy/msvc-dev-cmd@v1.10.0 + uses: ilammy/msvc-dev-cmd@v1.12.1 with: arch: x86 @@ -798,10 +839,10 @@ jobs: architecture: x86 - name: Update CMake - uses: jwlawson/actions-setup-cmake@v1.12 + uses: jwlawson/actions-setup-cmake@v1.14 - name: Prepare MSVC - uses: ilammy/msvc-dev-cmd@v1.10.0 + uses: ilammy/msvc-dev-cmd@v1.12.1 with: arch: x86 @@ -849,7 +890,7 @@ jobs: python3 -m pip install -r tests/requirements.txt - name: Update CMake - uses: jwlawson/actions-setup-cmake@v1.12 + uses: jwlawson/actions-setup-cmake@v1.14 - name: Configure C++20 run: > @@ -871,6 +912,21 @@ jobs: - name: Interface test C++20 run: cmake --build build --target test_cmake_build + - name: Configure C++20 - Exercise cmake -DPYBIND11_TEST_OVERRIDE + run: > + cmake -S . -B build_partial + -DPYBIND11_WERROR=ON + -DDOWNLOAD_CATCH=ON + -DDOWNLOAD_EIGEN=ON + -DCMAKE_CXX_STANDARD=20 + "-DPYBIND11_TEST_OVERRIDE=test_call_policies.cpp;test_gil_scoped.cpp;test_thread.cpp" + + - name: Build C++20 - Exercise cmake -DPYBIND11_TEST_OVERRIDE + run: cmake --build build_partial -j 2 + + - name: Python tests - Exercise cmake -DPYBIND11_TEST_OVERRIDE + run: cmake --build build_partial --target pytest + mingw: name: "🐍 3 • windows-latest • ${{ matrix.sys }}" runs-on: windows-latest @@ -905,7 +961,7 @@ jobs: - name: Configure C++11 # LTO leads to many undefined reference like # `pybind11::detail::function_call::function_call(pybind11::detail::function_call&&) - run: cmake -G "MinGW Makefiles" -DCMAKE_CXX_STANDARD=11 -DDOWNLOAD_CATCH=ON -S . -B build + run: cmake -G "MinGW Makefiles" -DCMAKE_CXX_STANDARD=11 -DPYBIND11_WERROR=ON -DDOWNLOAD_CATCH=ON -S . -B build - name: Build C++11 run: cmake --build build -j 2 @@ -923,7 +979,7 @@ jobs: run: git clean -fdx - name: Configure C++14 - run: cmake -G "MinGW Makefiles" -DCMAKE_CXX_STANDARD=14 -DDOWNLOAD_CATCH=ON -S . -B build2 + run: cmake -G "MinGW Makefiles" -DCMAKE_CXX_STANDARD=14 -DPYBIND11_WERROR=ON -DDOWNLOAD_CATCH=ON -S . -B build2 - name: Build C++14 run: cmake --build build2 -j 2 @@ -941,7 +997,7 @@ jobs: run: git clean -fdx - name: Configure C++17 - run: cmake -G "MinGW Makefiles" -DCMAKE_CXX_STANDARD=17 -DDOWNLOAD_CATCH=ON -S . -B build3 + run: cmake -G "MinGW Makefiles" -DCMAKE_CXX_STANDARD=17 -DPYBIND11_WERROR=ON -DDOWNLOAD_CATCH=ON -S . -B build3 - name: Build C++17 run: cmake --build build3 -j 2 @@ -954,3 +1010,156 @@ jobs: - name: Interface test C++17 run: PYTHONHOME=/${{matrix.sys}} PYTHONPATH=/${{matrix.sys}} cmake --build build3 --target test_cmake_build + + windows_clang: + + strategy: + matrix: + os: [windows-latest] + python: ['3.10'] + + runs-on: "${{ matrix.os }}" + + name: "🐍 ${{ matrix.python }} • ${{ matrix.os }} • clang-latest" + + steps: + - name: Show env + run: env + + - name: Checkout + uses: actions/checkout@v3 + + - name: Set up Clang + uses: egor-tensin/setup-clang@v1 + + - name: Setup Python ${{ matrix.python }} + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python }} + + - name: Update CMake + uses: jwlawson/actions-setup-cmake@v1.14 + + - name: Install ninja-build tool + uses: seanmiddleditch/gha-setup-ninja@v3 + + - name: Run pip installs + run: | + python -m pip install --upgrade pip + python -m pip install -r tests/requirements.txt + + - name: Show Clang++ version + run: clang++ --version + + - name: Show CMake version + run: cmake --version + + # TODO: WERROR=ON + - name: Configure Clang + run: > + cmake -G Ninja -S . -B . + -DPYBIND11_WERROR=OFF + -DPYBIND11_SIMPLE_GIL_MANAGEMENT=OFF + -DDOWNLOAD_CATCH=ON + -DDOWNLOAD_EIGEN=ON + -DCMAKE_CXX_COMPILER=clang++ + -DCMAKE_CXX_STANDARD=17 + + - name: Build + run: cmake --build . -j 2 + + - name: Python tests + run: cmake --build . --target pytest -j 2 + + - name: C++ tests + run: cmake --build . --target cpptest -j 2 + + - name: Interface test + run: cmake --build . --target test_cmake_build -j 2 + + - name: Clean directory + run: git clean -fdx + + macos_brew_install_llvm: + name: "macos-latest • brew install llvm" + runs-on: macos-latest + + env: + # https://apple.stackexchange.com/questions/227026/how-to-install-recent-clang-with-homebrew + LDFLAGS: '-L/usr/local/opt/llvm/lib -Wl,-rpath,/usr/local/opt/llvm/lib' + + steps: + - name: Update PATH + run: echo "/usr/local/opt/llvm/bin" >> $GITHUB_PATH + + - name: Show env + run: env + + - name: Checkout + uses: actions/checkout@v3 + + - name: Show Clang++ version before brew install llvm + run: clang++ --version + + - name: brew install llvm + run: brew install llvm + + - name: Show Clang++ version after brew install llvm + run: clang++ --version + + - name: Update CMake + uses: jwlawson/actions-setup-cmake@v1.14 + + - name: Run pip installs + run: | + python3 -m pip install --upgrade pip + python3 -m pip install -r tests/requirements.txt + python3 -m pip install numpy + python3 -m pip install scipy + + - name: Show CMake version + run: cmake --version + + - name: CMake Configure + run: > + cmake -S . -B . + -DPYBIND11_WERROR=ON + -DPYBIND11_SIMPLE_GIL_MANAGEMENT=OFF + -DDOWNLOAD_CATCH=ON + -DDOWNLOAD_EIGEN=ON + -DCMAKE_CXX_COMPILER=clang++ + -DCMAKE_CXX_STANDARD=17 + -DPYTHON_EXECUTABLE=$(python3 -c "import sys; print(sys.executable)") + + - name: Build + run: cmake --build . -j 2 + + - name: Python tests + run: cmake --build . --target pytest -j 2 + + - name: C++ tests + run: cmake --build . --target cpptest -j 2 + + - name: Interface test + run: cmake --build . --target test_cmake_build -j 2 + + - name: CMake Configure - Exercise cmake -DPYBIND11_TEST_OVERRIDE + run: > + cmake -S . -B build_partial + -DPYBIND11_WERROR=ON + -DPYBIND11_SIMPLE_GIL_MANAGEMENT=OFF + -DDOWNLOAD_CATCH=ON + -DDOWNLOAD_EIGEN=ON + -DCMAKE_CXX_COMPILER=clang++ + -DCMAKE_CXX_STANDARD=17 + -DPYTHON_EXECUTABLE=$(python3 -c "import sys; print(sys.executable)") + "-DPYBIND11_TEST_OVERRIDE=test_call_policies.cpp;test_gil_scoped.cpp;test_thread.cpp" + + - name: Build - Exercise cmake -DPYBIND11_TEST_OVERRIDE + run: cmake --build build_partial -j 2 + + - name: Python tests - Exercise cmake -DPYBIND11_TEST_OVERRIDE + run: cmake --build build_partial --target pytest -j 2 + + - name: Clean directory + run: git clean -fdx diff --git a/pybind11/.github/workflows/configure.yml b/pybind11/.github/workflows/configure.yml index edcad4198..ec7cd612d 100644 --- a/pybind11/.github/workflows/configure.yml +++ b/pybind11/.github/workflows/configure.yml @@ -9,6 +9,14 @@ on: - stable - v* +permissions: + contents: read + +env: + PIP_BREAK_SYSTEM_PACKAGES: 1 + # For cmake: + VERBOSE: 1 + jobs: # This tests various versions of CMake in various combinations, to make sure # the configure step passes. @@ -16,22 +24,26 @@ jobs: strategy: fail-fast: false matrix: - runs-on: [ubuntu-latest, macos-latest, windows-latest] + runs-on: [ubuntu-20.04, macos-latest, windows-latest] arch: [x64] - cmake: ["3.23"] + cmake: ["3.26"] include: - - runs-on: ubuntu-latest + - runs-on: ubuntu-20.04 arch: x64 - cmake: 3.4 + cmake: "3.5" + + - runs-on: ubuntu-20.04 + arch: x64 + cmake: "3.27" - runs-on: macos-latest arch: x64 - cmake: 3.7 + cmake: "3.7" - runs-on: windows-2019 arch: x64 # x86 compilers seem to be missing on 2019 image - cmake: 3.18 + cmake: "3.18" name: 🐍 3.7 • CMake ${{ matrix.cmake }} • ${{ matrix.runs-on }} runs-on: ${{ matrix.runs-on }} @@ -51,7 +63,7 @@ jobs: # An action for adding a specific version of CMake: # https://github.com/jwlawson/actions-setup-cmake - name: Setup CMake ${{ matrix.cmake }} - uses: jwlawson/actions-setup-cmake@v1.12 + uses: jwlawson/actions-setup-cmake@v1.14 with: cmake-version: ${{ matrix.cmake }} diff --git a/pybind11/.github/workflows/format.yml b/pybind11/.github/workflows/format.yml index 31d893c47..b8242ee52 100644 --- a/pybind11/.github/workflows/format.yml +++ b/pybind11/.github/workflows/format.yml @@ -12,8 +12,13 @@ on: - stable - "v*" +permissions: + contents: read + env: FORCE_COLOR: 3 + # For cmake: + VERBOSE: 1 jobs: pre-commit: @@ -36,12 +41,12 @@ jobs: # in .github/CONTRIBUTING.md and update as needed. name: Clang-Tidy runs-on: ubuntu-latest - container: silkeh/clang:13 + container: silkeh/clang:15-bullseye steps: - uses: actions/checkout@v3 - name: Install requirements - run: apt-get update && apt-get install -y python3-dev python3-pytest + run: apt-get update && apt-get install -y git python3-dev python3-pytest - name: Configure run: > diff --git a/pybind11/.github/workflows/labeler.yml b/pybind11/.github/workflows/labeler.yml index d2b597968..858a4a0e2 100644 --- a/pybind11/.github/workflows/labeler.yml +++ b/pybind11/.github/workflows/labeler.yml @@ -3,14 +3,23 @@ on: pull_request_target: types: [closed] +permissions: {} + jobs: label: name: Labeler runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: write steps: - uses: actions/labeler@main - if: github.event.pull_request.merged == true + if: > + github.event.pull_request.merged == true && + !startsWith(github.event.pull_request.title, 'chore(deps):') && + !startsWith(github.event.pull_request.title, 'ci(fix):') && + !startsWith(github.event.pull_request.title, 'docs(changelog):') with: repo-token: ${{ secrets.GITHUB_TOKEN }} configuration-path: .github/labeler_merged.yml diff --git a/pybind11/.github/workflows/pip.yml b/pybind11/.github/workflows/pip.yml index 2c16735e1..d6687b441 100644 --- a/pybind11/.github/workflows/pip.yml +++ b/pybind11/.github/workflows/pip.yml @@ -12,7 +12,11 @@ on: types: - published +permissions: + contents: read + env: + PIP_BREAK_SYSTEM_PACKAGES: 1 PIP_ONLY_BINARY: numpy jobs: @@ -98,13 +102,13 @@ jobs: - uses: actions/download-artifact@v3 - name: Publish standard package - uses: pypa/gh-action-pypi-publish@v1.5.0 + uses: pypa/gh-action-pypi-publish@release/v1 with: password: ${{ secrets.pypi_password }} - packages_dir: standard/ + packages-dir: standard/ - name: Publish global package - uses: pypa/gh-action-pypi-publish@v1.5.0 + uses: pypa/gh-action-pypi-publish@release/v1 with: password: ${{ secrets.pypi_password_global }} - packages_dir: global/ + packages-dir: global/ diff --git a/pybind11/.github/workflows/upstream.yml b/pybind11/.github/workflows/upstream.yml index a40a6c7d7..dd8a1c960 100644 --- a/pybind11/.github/workflows/upstream.yml +++ b/pybind11/.github/workflows/upstream.yml @@ -1,112 +1,116 @@ - name: Upstream on: workflow_dispatch: pull_request: +permissions: + contents: read + concurrency: group: upstream-${{ github.ref }} cancel-in-progress: true env: - PIP_ONLY_BINARY: numpy + PIP_BREAK_SYSTEM_PACKAGES: 1 + PIP_ONLY_BINARY: ":all:" + # For cmake: + VERBOSE: 1 jobs: standard: - name: "🐍 3.11 latest internals • ubuntu-latest • x64" + name: "🐍 3.12 latest • ubuntu-latest • x64" runs-on: ubuntu-latest + # Only runs when the 'python dev' label is selected if: "contains(github.event.pull_request.labels.*.name, 'python dev')" steps: - uses: actions/checkout@v3 - - name: Setup Python 3.11 + - name: Setup Python 3.12 uses: actions/setup-python@v4 with: - python-version: "3.11-dev" + python-version: "3.12-dev" - - name: Setup Boost (Linux) - if: runner.os == 'Linux' + - name: Setup Boost run: sudo apt-get install libboost-dev - name: Update CMake - uses: jwlawson/actions-setup-cmake@v1.12 + uses: jwlawson/actions-setup-cmake@v1.14 - - name: Prepare env + - name: Run pip installs run: | + python -m pip install --upgrade pip python -m pip install -r tests/requirements.txt - - name: Setup annotations on Linux - if: runner.os == 'Linux' - run: python -m pip install pytest-github-actions-annotate-failures + - name: Show platform info + run: | + python -m platform + cmake --version + pip list # First build - C++11 mode and inplace - name: Configure C++11 run: > - cmake -S . -B . + cmake -S . -B build11 -DPYBIND11_WERROR=ON -DDOWNLOAD_CATCH=ON -DDOWNLOAD_EIGEN=ON -DCMAKE_CXX_STANDARD=11 + -DCMAKE_BUILD_TYPE=Debug - name: Build C++11 - run: cmake --build . -j 2 + run: cmake --build build11 -j 2 - name: Python tests C++11 - run: cmake --build . --target pytest -j 2 + run: cmake --build build11 --target pytest -j 2 - name: C++11 tests - run: cmake --build . --target cpptest -j 2 + run: cmake --build build11 --target cpptest -j 2 - name: Interface test C++11 - run: cmake --build . --target test_cmake_build - - - name: Clean directory - run: git clean -fdx + run: cmake --build build11 --target test_cmake_build # Second build - C++17 mode and in a build directory - name: Configure C++17 run: > - cmake -S . -B build2 + cmake -S . -B build17 -DPYBIND11_WERROR=ON -DDOWNLOAD_CATCH=ON -DDOWNLOAD_EIGEN=ON -DCMAKE_CXX_STANDARD=17 - ${{ matrix.args }} - ${{ matrix.args2 }} - - name: Build - run: cmake --build build2 -j 2 + - name: Build C++17 + run: cmake --build build17 -j 2 - - name: Python tests - run: cmake --build build2 --target pytest + - name: Python tests C++17 + run: cmake --build build17 --target pytest - - name: C++ tests - run: cmake --build build2 --target cpptest + - name: C++17 tests + run: cmake --build build17 --target cpptest # Third build - C++17 mode with unstable ABI - name: Configure (unstable ABI) run: > - cmake -S . -B build3 + cmake -S . -B build17max -DPYBIND11_WERROR=ON -DDOWNLOAD_CATCH=ON -DDOWNLOAD_EIGEN=ON -DCMAKE_CXX_STANDARD=17 -DPYBIND11_INTERNALS_VERSION=10000000 - "-DPYBIND11_TEST_OVERRIDE=test_call_policies.cpp;test_gil_scoped.cpp;test_thread.cpp" - ${{ matrix.args }} - name: Build (unstable ABI) - run: cmake --build build3 -j 2 + run: cmake --build build17max -j 2 - name: Python tests (unstable ABI) - run: cmake --build build3 --target pytest + run: cmake --build build17max --target pytest - - name: Interface test - run: cmake --build build2 --target test_cmake_build + - name: Interface test (unstable ABI) + run: cmake --build build17max --target test_cmake_build # This makes sure the setup_helpers module can build packages using # setuptools - name: Setuptools helpers test - run: pytest tests/extra_setuptools + run: | + pip install setuptools + pytest tests/extra_setuptools diff --git a/pybind11/.gitignore b/pybind11/.gitignore index 3cf4fbbda..43d5094c9 100644 --- a/pybind11/.gitignore +++ b/pybind11/.gitignore @@ -43,3 +43,4 @@ pybind11Targets.cmake /pybind11/share/* /docs/_build/* .ipynb_checkpoints/ +tests/main.cpp diff --git a/pybind11/.pre-commit-config.yaml b/pybind11/.pre-commit-config.yaml index ba9955a31..86ac965d9 100644 --- a/pybind11/.pre-commit-config.yaml +++ b/pybind11/.pre-commit-config.yaml @@ -12,10 +12,62 @@ # # See https://github.com/pre-commit/pre-commit + +ci: + autoupdate_commit_msg: "chore(deps): update pre-commit hooks" + autofix_commit_msg: "style: pre-commit fixes" + autoupdate_schedule: monthly + +# third-party content +exclude: ^tools/JoinPaths.cmake$ + repos: + +# Clang format the codebase automatically +- repo: https://github.com/pre-commit/mirrors-clang-format + rev: "v16.0.6" + hooks: + - id: clang-format + types_or: [c++, c, cuda] + +# Black, the code formatter, natively supports pre-commit +- repo: https://github.com/psf/black + rev: "23.3.0" # Keep in sync with blacken-docs + hooks: + - id: black + +# Ruff, the Python auto-correcting linter written in Rust +- repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.0.276 + hooks: + - id: ruff + args: ["--fix", "--show-fixes"] + +# Check static types with mypy +- repo: https://github.com/pre-commit/mirrors-mypy + rev: "v1.4.1" + hooks: + - id: mypy + args: [] + exclude: ^(tests|docs)/ + additional_dependencies: + - markdown-it-py<3 # Drop this together with dropping Python 3.7 support. + - nox + - rich + - types-setuptools + +# CMake formatting +- repo: https://github.com/cheshirekow/cmake-format-precommit + rev: "v0.6.13" + hooks: + - id: cmake-format + additional_dependencies: [pyyaml] + types: [file] + files: (\.cmake|CMakeLists.txt)(.in)?$ + # Standard hooks - repo: https://github.com/pre-commit/pre-commit-hooks - rev: "v4.3.0" + rev: "v4.4.0" hooks: - id: check-added-large-files - id: check-case-conflict @@ -30,109 +82,38 @@ repos: - id: requirements-txt-fixer - id: trailing-whitespace -# Upgrade old Python syntax -- repo: https://github.com/asottile/pyupgrade - rev: "v2.37.1" - hooks: - - id: pyupgrade - args: [--py36-plus] - -# Nicely sort includes -- repo: https://github.com/PyCQA/isort - rev: "5.10.1" - hooks: - - id: isort - -# Black, the code formatter, natively supports pre-commit -- repo: https://github.com/psf/black - rev: "22.6.0" # Keep in sync with blacken-docs - hooks: - - id: black - # Also code format the docs - repo: https://github.com/asottile/blacken-docs - rev: "v1.12.1" + rev: "1.14.0" hooks: - id: blacken-docs additional_dependencies: - - black==22.6.0 # keep in sync with black hook + - black==23.3.0 # keep in sync with black hook # Changes tabs to spaces - repo: https://github.com/Lucas-C/pre-commit-hooks - rev: "v1.3.0" + rev: "v1.5.1" hooks: - id: remove-tabs +# Avoid directional quotes - repo: https://github.com/sirosen/texthooks - rev: "0.3.1" + rev: "0.5.0" hooks: - id: fix-ligatures - id: fix-smartquotes -# Autoremoves unused imports -- repo: https://github.com/hadialqattan/pycln - rev: "v2.0.1" - hooks: - - id: pycln - stages: [manual] - # Checking for common mistakes - repo: https://github.com/pre-commit/pygrep-hooks - rev: "v1.9.0" + rev: "v1.10.0" hooks: - - id: python-check-blanket-noqa - - id: python-check-blanket-type-ignore - - id: python-no-log-warn - - id: python-use-type-annotations - id: rst-backticks - id: rst-directive-colons - id: rst-inline-touching-normal -# Automatically remove noqa that are not used -- repo: https://github.com/asottile/yesqa - rev: "v1.3.0" - hooks: - - id: yesqa - additional_dependencies: &flake8_dependencies - - flake8-bugbear - - pep8-naming - -# Flake8 also supports pre-commit natively (same author) -- repo: https://github.com/PyCQA/flake8 - rev: "4.0.1" - hooks: - - id: flake8 - exclude: ^(docs/.*|tools/.*)$ - additional_dependencies: *flake8_dependencies - -# PyLint has native support - not always usable, but works for us -- repo: https://github.com/PyCQA/pylint - rev: "v2.14.4" - hooks: - - id: pylint - files: ^pybind11 - -# CMake formatting -- repo: https://github.com/cheshirekow/cmake-format-precommit - rev: "v0.6.13" - hooks: - - id: cmake-format - additional_dependencies: [pyyaml] - types: [file] - files: (\.cmake|CMakeLists.txt)(.in)?$ - -# Check static types with mypy -- repo: https://github.com/pre-commit/mirrors-mypy - rev: "v0.961" - hooks: - - id: mypy - args: [] - exclude: ^(tests|docs)/ - additional_dependencies: [nox, rich] - # Checks the manifest for missing files (native support) - repo: https://github.com/mgedmin/check-manifest - rev: "0.48" + rev: "0.49" hooks: - id: check-manifest # This is a slow hook, so only run this if --hook-stage manual is passed @@ -140,16 +121,18 @@ repos: additional_dependencies: [cmake, ninja] # Check for spelling +# Use tools/codespell_ignore_lines_from_errors.py +# to rebuild .codespell-ignore-lines - repo: https://github.com/codespell-project/codespell - rev: "v2.1.0" + rev: "v2.2.5" hooks: - id: codespell exclude: ".supp$" - args: ["-L", "nd,ot,thist"] + args: ["-x.codespell-ignore-lines", "-Lccompiler"] # Check for common shell mistakes - repo: https://github.com/shellcheck-py/shellcheck-py - rev: "v0.8.0.4" + rev: "v0.9.0.5" hooks: - id: shellcheck @@ -162,9 +145,9 @@ repos: entry: PyBind|Numpy|Cmake|CCache|PyTest exclude: ^\.pre-commit-config.yaml$ -# Clang format the codebase automatically -- repo: https://github.com/pre-commit/mirrors-clang-format - rev: "v14.0.6" +# PyLint has native support - not always usable, but works for us +- repo: https://github.com/PyCQA/pylint + rev: "v3.0.0a6" hooks: - - id: clang-format - types_or: [c++, c, cuda] + - id: pylint + files: ^pybind11 diff --git a/pybind11/CMakeLists.txt b/pybind11/CMakeLists.txt index 3787982cb..87ec10346 100644 --- a/pybind11/CMakeLists.txt +++ b/pybind11/CMakeLists.txt @@ -5,15 +5,15 @@ # All rights reserved. Use of this source code is governed by a # BSD-style license that can be found in the LICENSE file. -cmake_minimum_required(VERSION 3.4) +cmake_minimum_required(VERSION 3.5) -# The `cmake_minimum_required(VERSION 3.4...3.22)` syntax does not work with +# The `cmake_minimum_required(VERSION 3.5...3.26)` syntax does not work with # some versions of VS that have a patched CMake 3.11. This forces us to emulate # the behavior using the following workaround: -if(${CMAKE_VERSION} VERSION_LESS 3.22) +if(${CMAKE_VERSION} VERSION_LESS 3.26) cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}) else() - cmake_policy(VERSION 3.22) + cmake_policy(VERSION 3.26) endif() # Avoid infinite recursion if tests include this as a subdirectory @@ -91,10 +91,16 @@ endif() option(PYBIND11_INSTALL "Install pybind11 header files?" ${PYBIND11_MASTER_PROJECT}) option(PYBIND11_TEST "Build pybind11 test suite?" ${PYBIND11_MASTER_PROJECT}) option(PYBIND11_NOPYTHON "Disable search for Python" OFF) +option(PYBIND11_SIMPLE_GIL_MANAGEMENT + "Use simpler GIL management logic that does not support disassociation" OFF) set(PYBIND11_INTERNALS_VERSION "" CACHE STRING "Override the ABI version, may be used to enable the unstable ABI.") +if(PYBIND11_SIMPLE_GIL_MANAGEMENT) + add_compile_definitions(PYBIND11_SIMPLE_GIL_MANAGEMENT) +endif() + cmake_dependent_option( USE_PYTHON_INCLUDE_DIR "Install pybind11 headers in Python include directory instead of default installation prefix" @@ -120,6 +126,9 @@ set(PYBIND11_HEADERS include/pybind11/complex.h include/pybind11/options.h include/pybind11/eigen.h + include/pybind11/eigen/common.h + include/pybind11/eigen/matrix.h + include/pybind11/eigen/tensor.h include/pybind11/embed.h include/pybind11/eval.h include/pybind11/gil.h @@ -131,7 +140,8 @@ set(PYBIND11_HEADERS include/pybind11/pytypes.h include/pybind11/stl.h include/pybind11/stl_bind.h - include/pybind11/stl/filesystem.h) + include/pybind11/stl/filesystem.h + include/pybind11/type_caster_pyobject_ptr.h) # Compare with grep and warn if mismatched if(PYBIND11_MASTER_PROJECT AND NOT CMAKE_VERSION VERSION_LESS 3.12) @@ -198,6 +208,9 @@ else() endif() include("${CMAKE_CURRENT_SOURCE_DIR}/tools/pybind11Common.cmake") +# https://github.com/jtojnar/cmake-snips/#concatenating-paths-when-building-pkg-config-files +# TODO: cmake 3.20 adds the cmake_path() function, which obsoletes this snippet +include("${CMAKE_CURRENT_SOURCE_DIR}/tools/JoinPaths.cmake") # Relative directory setting if(USE_PYTHON_INCLUDE_DIR AND DEFINED Python_INCLUDE_DIRS) @@ -262,6 +275,16 @@ if(PYBIND11_INSTALL) NAMESPACE "pybind11::" DESTINATION ${PYBIND11_CMAKECONFIG_INSTALL_DIR}) + # pkg-config support + if(NOT prefix_for_pc_file) + set(prefix_for_pc_file "${CMAKE_INSTALL_PREFIX}") + endif() + join_paths(includedir_for_pc_file "\${prefix}" "${CMAKE_INSTALL_INCLUDEDIR}") + configure_file("${CMAKE_CURRENT_SOURCE_DIR}/tools/pybind11.pc.in" + "${CMAKE_CURRENT_BINARY_DIR}/pybind11.pc" @ONLY) + install(FILES "${CMAKE_CURRENT_BINARY_DIR}/pybind11.pc" + DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/pkgconfig/") + # Uninstall target if(PYBIND11_MASTER_PROJECT) configure_file("${CMAKE_CURRENT_SOURCE_DIR}/tools/cmake_uninstall.cmake.in" diff --git a/pybind11/MANIFEST.in b/pybind11/MANIFEST.in index 033303a74..7ce83c552 100644 --- a/pybind11/MANIFEST.in +++ b/pybind11/MANIFEST.in @@ -1,5 +1,6 @@ +prune tests recursive-include pybind11/include/pybind11 *.h recursive-include pybind11 *.py recursive-include pybind11 py.typed include pybind11/share/cmake/pybind11/*.cmake -include LICENSE README.rst pyproject.toml setup.py setup.cfg +include LICENSE README.rst SECURITY.md pyproject.toml setup.py setup.cfg diff --git a/pybind11/README.rst b/pybind11/README.rst index 3c75edb57..80213a406 100644 --- a/pybind11/README.rst +++ b/pybind11/README.rst @@ -135,7 +135,7 @@ This project was created by `Wenzel Jakob `_. Significant features and/or improvements to the code were contributed by Jonas Adler, Lori A. Burns, Sylvain Corlay, Eric Cousineau, Aaron Gokaslan, Ralf Grosse-Kunstleve, Trent Houliston, Axel -Huebl, @hulucc, Yannick Jadoul, Sergey Lyskov Johan Mabille, Tomasz Miąsko, +Huebl, @hulucc, Yannick Jadoul, Sergey Lyskov, Johan Mabille, Tomasz Miąsko, Dean Moldovan, Ben Pritchard, Jason Rhinelander, Boris Schäling, Pim Schellart, Henry Schreiner, Ivan Smirnov, Boris Staletic, and Patrick Stewart. diff --git a/pybind11/SECURITY.md b/pybind11/SECURITY.md new file mode 100644 index 000000000..3d74611f2 --- /dev/null +++ b/pybind11/SECURITY.md @@ -0,0 +1,13 @@ +# Security Policy + +## Supported Versions + +Security updates are applied only to the latest release. + +## Reporting a Vulnerability + +If you have discovered a security vulnerability in this project, please report it privately. **Do not disclose it as a public issue.** This gives us time to work with you to fix the issue before public exposure, reducing the chance that the exploit will be used before a patch is released. + +Please disclose it at [security advisory](https://github.com/pybind/pybind11/security/advisories/new). + +This project is maintained by a team of volunteers on a reasonable-effort basis. As such, please give us at least 90 days to work on a fix before public exposure. diff --git a/pybind11/docs/advanced/cast/custom.rst b/pybind11/docs/advanced/cast/custom.rst index 1df4d3e14..8138cac61 100644 --- a/pybind11/docs/advanced/cast/custom.rst +++ b/pybind11/docs/advanced/cast/custom.rst @@ -38,7 +38,7 @@ type is explicitly allowed. .. code-block:: cpp - namespace pybind11 { namespace detail { + namespace PYBIND11_NAMESPACE { namespace detail { template <> struct type_caster { public: /** @@ -78,7 +78,7 @@ type is explicitly allowed. return PyLong_FromLong(src.long_value); } }; - }} // namespace pybind11::detail + }} // namespace PYBIND11_NAMESPACE::detail .. note:: diff --git a/pybind11/docs/advanced/cast/stl.rst b/pybind11/docs/advanced/cast/stl.rst index 109763f7a..03d49b295 100644 --- a/pybind11/docs/advanced/cast/stl.rst +++ b/pybind11/docs/advanced/cast/stl.rst @@ -42,7 +42,7 @@ types: .. code-block:: cpp // `boost::optional` as an example -- can be any `std::optional`-like container - namespace pybind11 { namespace detail { + namespace PYBIND11_NAMESPACE { namespace detail { template struct type_caster> : optional_caster> {}; }} @@ -54,7 +54,7 @@ for custom variant types: .. code-block:: cpp // `boost::variant` as an example -- can be any `std::variant`-like container - namespace pybind11 { namespace detail { + namespace PYBIND11_NAMESPACE { namespace detail { template struct type_caster> : variant_caster> {}; @@ -66,7 +66,7 @@ for custom variant types: return boost::apply_visitor(args...); } }; - }} // namespace pybind11::detail + }} // namespace PYBIND11_NAMESPACE::detail The ``visit_helper`` specialization is not required if your ``name::variant`` provides a ``name::visit()`` function. For any other function name, the specialization must be diff --git a/pybind11/docs/advanced/cast/strings.rst b/pybind11/docs/advanced/cast/strings.rst index e246c5219..271716b4b 100644 --- a/pybind11/docs/advanced/cast/strings.rst +++ b/pybind11/docs/advanced/cast/strings.rst @@ -101,8 +101,11 @@ conversion has the same overhead as implicit conversion. m.def("str_output", []() { std::string s = "Send your r\xe9sum\xe9 to Alice in HR"; // Latin-1 - py::str py_s = PyUnicode_DecodeLatin1(s.data(), s.length()); - return py_s; + py::handle py_s = PyUnicode_DecodeLatin1(s.data(), s.length(), nullptr); + if (!py_s) { + throw py::error_already_set(); + } + return py::reinterpret_steal(py_s); } ); @@ -113,7 +116,8 @@ conversion has the same overhead as implicit conversion. The `Python C API `_ provides -several built-in codecs. +several built-in codecs. Note that these all return *new* references, so +use :cpp:func:`reinterpret_steal` when converting them to a :cpp:class:`str`. One could also use a third party encoding library such as libiconv to transcode diff --git a/pybind11/docs/advanced/classes.rst b/pybind11/docs/advanced/classes.rst index 49ddf5c0b..01a490b72 100644 --- a/pybind11/docs/advanced/classes.rst +++ b/pybind11/docs/advanced/classes.rst @@ -1228,7 +1228,7 @@ whether a downcast is safe, you can proceed by specializing the std::string bark() const { return sound; } }; - namespace pybind11 { + namespace PYBIND11_NAMESPACE { template<> struct polymorphic_type_hook { static const void *get(const Pet *src, const std::type_info*& type) { // note that src may be nullptr @@ -1239,7 +1239,7 @@ whether a downcast is safe, you can proceed by specializing the return src; } }; - } // namespace pybind11 + } // namespace PYBIND11_NAMESPACE When pybind11 wants to convert a C++ pointer of type ``Base*`` to a Python object, it calls ``polymorphic_type_hook::get()`` to diff --git a/pybind11/docs/advanced/embedding.rst b/pybind11/docs/advanced/embedding.rst index dd980d483..e6a1686f8 100644 --- a/pybind11/docs/advanced/embedding.rst +++ b/pybind11/docs/advanced/embedding.rst @@ -18,7 +18,7 @@ information, see :doc:`/compiling`. .. code-block:: cmake - cmake_minimum_required(VERSION 3.4) + cmake_minimum_required(VERSION 3.5...3.26) project(example) find_package(pybind11 REQUIRED) # or `add_subdirectory(pybind11)` diff --git a/pybind11/docs/advanced/exceptions.rst b/pybind11/docs/advanced/exceptions.rst index 2211caf5d..53981dc08 100644 --- a/pybind11/docs/advanced/exceptions.rst +++ b/pybind11/docs/advanced/exceptions.rst @@ -177,9 +177,12 @@ section. may be explicitly (re-)thrown to delegate it to the other, previously-declared existing exception translators. - Note that ``libc++`` and ``libstdc++`` `behave differently `_ - with ``-fvisibility=hidden``. Therefore exceptions that are used across ABI boundaries need to be explicitly exported, as exercised in ``tests/test_exceptions.h``. - See also: "Problems with C++ exceptions" under `GCC Wiki `_. + Note that ``libc++`` and ``libstdc++`` `behave differently under macOS + `_ + with ``-fvisibility=hidden``. Therefore exceptions that are used across ABI + boundaries need to be explicitly exported, as exercised in + ``tests/test_exceptions.h``. See also: + "Problems with C++ exceptions" under `GCC Wiki `_. Local vs Global Exception Translators diff --git a/pybind11/docs/advanced/misc.rst b/pybind11/docs/advanced/misc.rst index edab15fcb..805ec838f 100644 --- a/pybind11/docs/advanced/misc.rst +++ b/pybind11/docs/advanced/misc.rst @@ -39,15 +39,42 @@ The ``PYBIND11_MAKE_OPAQUE`` macro does *not* require the above workarounds. Global Interpreter Lock (GIL) ============================= -When calling a C++ function from Python, the GIL is always held. +The Python C API dictates that the Global Interpreter Lock (GIL) must always +be held by the current thread to safely access Python objects. As a result, +when Python calls into C++ via pybind11 the GIL must be held, and pybind11 +will never implicitly release the GIL. + +.. code-block:: cpp + + void my_function() { + /* GIL is held when this function is called from Python */ + } + + PYBIND11_MODULE(example, m) { + m.def("my_function", &my_function); + } + +pybind11 will ensure that the GIL is held when it knows that it is calling +Python code. For example, if a Python callback is passed to C++ code via +``std::function``, when C++ code calls the function the built-in wrapper +will acquire the GIL before calling the Python callback. Similarly, the +``PYBIND11_OVERRIDE`` family of macros will acquire the GIL before calling +back into Python. + +When writing C++ code that is called from other C++ code, if that code accesses +Python state, it must explicitly acquire and release the GIL. + The classes :class:`gil_scoped_release` and :class:`gil_scoped_acquire` can be used to acquire and release the global interpreter lock in the body of a C++ function call. In this way, long-running C++ code can be parallelized using -multiple Python threads. Taking :ref:`overriding_virtuals` as an example, this +multiple Python threads, **but great care must be taken** when any +:class:`gil_scoped_release` appear: if there is any way that the C++ code +can access Python objects, :class:`gil_scoped_acquire` should be used to +reacquire the GIL. Taking :ref:`overriding_virtuals` as an example, this could be realized as follows (important changes highlighted): .. code-block:: cpp - :emphasize-lines: 8,9,31,32 + :emphasize-lines: 8,30,31 class PyAnimal : public Animal { public: @@ -56,9 +83,7 @@ could be realized as follows (important changes highlighted): /* Trampoline (need one for each virtual function) */ std::string go(int n_times) { - /* Acquire GIL before calling Python code */ - py::gil_scoped_acquire acquire; - + /* PYBIND11_OVERRIDE_PURE will acquire the GIL before accessing Python state */ PYBIND11_OVERRIDE_PURE( std::string, /* Return type */ Animal, /* Parent class */ @@ -78,7 +103,8 @@ could be realized as follows (important changes highlighted): .def(py::init<>()); m.def("call_go", [](Animal *animal) -> std::string { - /* Release GIL before calling into (potentially long-running) C++ code */ + // GIL is held when called from Python code. Release GIL before + // calling into (potentially long-running) C++ code py::gil_scoped_release release; return call_go(animal); }); @@ -92,6 +118,34 @@ The ``call_go`` wrapper can also be simplified using the ``call_guard`` policy m.def("call_go", &call_go, py::call_guard()); +Common Sources Of Global Interpreter Lock Errors +================================================================== + +Failing to properly hold the Global Interpreter Lock (GIL) is one of the +more common sources of bugs within code that uses pybind11. If you are +running into GIL related errors, we highly recommend you consult the +following checklist. + +- Do you have any global variables that are pybind11 objects or invoke + pybind11 functions in either their constructor or destructor? You are generally + not allowed to invoke any Python function in a global static context. We recommend + using lazy initialization and then intentionally leaking at the end of the program. + +- Do you have any pybind11 objects that are members of other C++ structures? One + commonly overlooked requirement is that pybind11 objects have to increase their reference count + whenever their copy constructor is called. Thus, you need to be holding the GIL to invoke + the copy constructor of any C++ class that has a pybind11 member. This can sometimes be very + tricky to track for complicated programs Think carefully when you make a pybind11 object + a member in another struct. + +- C++ destructors that invoke Python functions can be particularly troublesome as + destructors can sometimes get invoked in weird and unexpected circumstances as a result + of exceptions. + +- You should try running your code in a debug build. That will enable additional assertions + within pybind11 that will throw exceptions on certain GIL handling errors + (reference counting operations). + Binding sequence data types, iterators, the slicing protocol, etc. ================================================================== @@ -298,6 +352,15 @@ The class ``options`` allows you to selectively suppress auto-generated signatur m.def("add", [](int a, int b) { return a + b; }, "A function which adds two numbers"); } +pybind11 also appends all members of an enum to the resulting enum docstring. +This default behavior can be disabled by using the ``disable_enum_members_docstring()`` +function of the ``options`` class. + +With ``disable_user_defined_docstrings()`` all user defined docstrings of +``module_::def()``, ``class_::def()`` and ``enum_()`` are disabled, but the +function signatures and enum members are included in the docstring, unless they +are disabled separately. + Note that changes to the settings affect only function bindings created during the lifetime of the ``options`` instance. When it goes out of scope at the end of the module's init function, the default settings are restored to prevent unwanted side effects. diff --git a/pybind11/docs/advanced/pycpp/numpy.rst b/pybind11/docs/advanced/pycpp/numpy.rst index b6ef019ed..07c969305 100644 --- a/pybind11/docs/advanced/pycpp/numpy.rst +++ b/pybind11/docs/advanced/pycpp/numpy.rst @@ -433,7 +433,7 @@ following: { 2, 4 }, // shape (rows, cols) { sizeof(uint8_t) * 4, sizeof(uint8_t) } // strides in bytes ); - }) + }); This approach is meant for providing a ``memoryview`` for a C/C++ buffer not managed by Python. The user is responsible for managing the lifetime of the @@ -449,7 +449,7 @@ We can also use ``memoryview::from_memory`` for a simple 1D contiguous buffer: buffer, // buffer pointer sizeof(uint8_t) * 8 // buffer size ); - }) + }); .. versionchanged:: 2.6 ``memoryview::from_memory`` added. diff --git a/pybind11/docs/advanced/smart_ptrs.rst b/pybind11/docs/advanced/smart_ptrs.rst index 5a2220109..3c40ce123 100644 --- a/pybind11/docs/advanced/smart_ptrs.rst +++ b/pybind11/docs/advanced/smart_ptrs.rst @@ -157,7 +157,7 @@ specialized: PYBIND11_DECLARE_HOLDER_TYPE(T, SmartPtr); // Only needed if the type's `.get()` goes by another name - namespace pybind11 { namespace detail { + namespace PYBIND11_NAMESPACE { namespace detail { template struct holder_helper> { // <-- specialization static const T *get(const SmartPtr &p) { return p.getPointer(); } diff --git a/pybind11/docs/changelog.rst b/pybind11/docs/changelog.rst index b926b27af..add3fd66b 100644 --- a/pybind11/docs/changelog.rst +++ b/pybind11/docs/changelog.rst @@ -9,6 +9,364 @@ Starting with version 1.8.0, pybind11 releases use a `semantic versioning Changes will be added here periodically from the "Suggested changelog entry" block in pull request descriptions. + +Version 2.11.1 (July 17, 2023) +----------------------------- + +Changes: + +* ``PYBIND11_NO_ASSERT_GIL_HELD_INCREF_DECREF`` is now provided as an option + for disabling the default-on ``PyGILState_Check()``'s in + ``pybind11::handle``'s ``inc_ref()`` & ``dec_ref()``. + `#4753 `_ + +* ``PYBIND11_ASSERT_GIL_HELD_INCREF_DECREF`` was disabled for PyPy in general + (not just PyPy Windows). + `#4751 `_ + + +Version 2.11.0 (July 14, 2023) +----------------------------- + +New features: + +* The newly added ``pybind11::detail::is_move_constructible`` trait can be + specialized for cases in which ``std::is_move_constructible`` does not work + as needed. This is very similar to the long-established + ``pybind11::detail::is_copy_constructible``. + `#4631 `_ + +* Introduce ``recursive_container_traits``. + `#4623 `_ + +* ``pybind11/type_caster_pyobject_ptr.h`` was added to support automatic + wrapping of APIs that make use of ``PyObject *``. This header needs to + included explicitly (i.e. it is not included implicitly + with ``pybind/pybind11.h``). + `#4601 `_ + +* ``format_descriptor<>`` & ``npy_format_descriptor<>`` ``PyObject *`` + specializations were added. The latter enables ``py::array_t`` + to/from-python conversions. + `#4674 `_ + +* ``buffer_info`` gained an ``item_type_is_equivalent_to()`` member + function. + `#4674 `_ + +* The ``capsule`` API gained a user-friendly constructor + (``py::capsule(ptr, "name", dtor)``). + `#4720 `_ + +Changes: + +* ``PyGILState_Check()``'s in ``pybind11::handle``'s ``inc_ref()`` & + ``dec_ref()`` are now enabled by default again. + `#4246 `_ + +* ``py::initialize_interpreter()`` using ``PyConfig_InitPythonConfig()`` + instead of ``PyConfig_InitIsolatedConfig()``, to obtain complete + ``sys.path``. + `#4473 `_ + +* Cast errors now always include Python type information, even if + ``PYBIND11_DETAILED_ERROR_MESSAGES`` is not defined. This increases binary + sizes slightly (~1.5%) but the error messages are much more informative. + `#4463 `_ + +* The docstring generation for the ``std::array``-list caster was fixed. + Previously, signatures included the size of the list in a non-standard, + non-spec compliant way. The new format conforms to PEP 593. + **Tooling for processing the docstrings may need to be updated accordingly.** + `#4679 `_ + +* Setter return values (which are inaccessible for all practical purposes) are + no longer converted to Python (only to be discarded). + `#4621 `_ + +* Allow lambda specified to function definition to be ``noexcept(true)`` + in C++17. + `#4593 `_ + +* Get rid of recursive template instantiations for concatenating type + signatures on C++17 and higher. + `#4587 `_ + +* Compatibility with Python 3.12 (beta). Note that the minimum pybind11 + ABI version for Python 3.12 is version 5. (The default ABI version + for Python versions up to and including 3.11 is still version 4.). + `#4570 `_ + +* With ``PYBIND11_INTERNALS_VERSION 5`` (default for Python 3.12+), MSVC builds + use ``std::hash`` and ``std::equal_to`` + instead of string-based type comparisons. This resolves issues when binding + types defined in the unnamed namespace. + `#4319 `_ + +* Python exception ``__notes__`` (introduced with Python 3.11) are now added to + the ``error_already_set::what()`` output. + `#4678 `_ + +Build system improvements: + +* CMake 3.27 support was added, CMake 3.4 support was dropped. + FindPython will be used if ``FindPythonInterp`` is not present. + `#4719 `_ + +* Update clang-tidy to 15 in CI. + `#4387 `_ + +* Moved the linting framework over to Ruff. + `#4483 `_ + +* Skip ``lto`` checks and target generation when + ``CMAKE_INTERPROCEDURAL_OPTIMIZATION`` is defined. + `#4643 `_ + +* No longer inject ``-stdlib=libc++``, not needed for modern Pythons + (macOS 10.9+). + `#4639 `_ + +* PyPy 3.10 support was added, PyPy 3.7 support was dropped. + `#4728 `_ + +* Testing with Python 3.12 beta releases was added. + `#4713 `_ + + +Version 2.10.4 (Mar 16, 2023) +----------------------------- + +Changes: + +* ``python3 -m pybind11`` gained a ``--version`` option (prints the version and + exits). + `#4526 `_ + +Bug Fixes: + +* Fix a warning when pydebug is enabled on Python 3.11. + `#4461 `_ + +* Ensure ``gil_scoped_release`` RAII is non-copyable. + `#4490 `_ + +* Ensure the tests dir does not show up with new versions of setuptools. + `#4510 `_ + +* Better stacklevel for a warning in setuptools helpers. + `#4516 `_ + +Version 2.10.3 (Jan 3, 2023) +---------------------------- + +Changes: + +* Temporarily made our GIL status assertions (added in 2.10.2) disabled by + default (re-enable manually by defining + ``PYBIND11_ASSERT_GIL_HELD_INCREF_DECREF``, will be enabled in 2.11). + `#4432 `_ + +* Improved error messages when ``inc_ref``/``dec_ref`` are called with an + invalid GIL state. + `#4427 `_ + `#4436 `_ + +Bug Fixes: + +* Some minor touchups found by static analyzers. + `#4440 `_ + + +Version 2.10.2 (Dec 20, 2022) +----------------------------- + +Changes: + +* ``scoped_interpreter`` constructor taking ``PyConfig``. + `#4330 `_ + +* ``pybind11/eigen/tensor.h`` adds converters to and from ``Eigen::Tensor`` and + ``Eigen::TensorMap``. + `#4201 `_ + +* ``PyGILState_Check()``'s were integrated to ``pybind11::handle`` + ``inc_ref()`` & ``dec_ref()``. The added GIL checks are guarded by + ``PYBIND11_ASSERT_GIL_HELD_INCREF_DECREF``, which is the default only if + ``NDEBUG`` is not defined. (Made non-default in 2.10.3, will be active in 2.11) + `#4246 `_ + +* Add option for enable/disable enum members in docstring. + `#2768 `_ + +* Fixed typing of ``KeysView``, ``ValuesView`` and ``ItemsView`` in ``bind_map``. + `#4353 `_ + +Bug fixes: + +* Bug fix affecting only Python 3.6 under very specific, uncommon conditions: + move ``PyEval_InitThreads()`` call to the correct location. + `#4350 `_ + +* Fix segfault bug when passing foreign native functions to functional.h. + `#4254 `_ + +Build system improvements: + +* Support setting PYTHON_LIBRARIES manually for Windows ARM cross-compilation + (classic mode). + `#4406 `_ + +* Extend IPO/LTO detection for ICX (a.k.a IntelLLVM) compiler. + `#4402 `_ + +* Allow calling ``find_package(pybind11 CONFIG)`` multiple times from separate + directories in the same CMake project and properly link Python (new mode). + `#4401 `_ + +* ``multiprocessing_set_spawn`` in pytest fixture for added safety. + `#4377 `_ + +* Fixed a bug in two pybind11/tools cmake scripts causing "Unknown arguments specified" errors. + `#4327 `_ + + + +Version 2.10.1 (Oct 31, 2022) +----------------------------- + +This is the first version to fully support embedding the newly released Python 3.11. + +Changes: + +* Allow ``pybind11::capsule`` constructor to take null destructor pointers. + `#4221 `_ + +* ``embed.h`` was changed so that ``PYTHONPATH`` is used also with Python 3.11 + (established behavior). + `#4119 `_ + +* A ``PYBIND11_SIMPLE_GIL_MANAGEMENT`` option was added (cmake, C++ define), + along with many additional tests in ``test_gil_scoped.py``. The option may be + useful to try when debugging GIL-related issues, to determine if the more + complex default implementation is or is not to blame. See #4216 for + background. WARNING: Please be careful to not create ODR violations when + using the option: everything that is linked together with mutual symbol + visibility needs to be rebuilt. + `#4216 `_ + +* ``PYBIND11_EXPORT_EXCEPTION`` was made non-empty only under macOS. This makes + Linux builds safer, and enables the removal of warning suppression pragmas for + Windows. + `#4298 `_ + +Bug fixes: + +* Fixed a bug where ``UnicodeDecodeError`` was not propagated from various + ``py::str`` ctors when decoding surrogate utf characters. + `#4294 `_ + +* Revert perfect forwarding for ``make_iterator``. This broke at least one + valid use case. May revisit later. + `#4234 `_ + +* Fix support for safe casts to ``void*`` (regression in 2.10.0). + `#4275 `_ + +* Fix ``char8_t`` support (regression in 2.9). + `#4278 `_ + +* Unicode surrogate character in Python exception message leads to process + termination in ``error_already_set::what()``. + `#4297 `_ + +* Fix MSVC 2019 v.1924 & C++14 mode error for ``overload_cast``. + `#4188 `_ + +* Make augmented assignment operators non-const for the object-api. Behavior + was previously broken for augmented assignment operators. + `#4065 `_ + +* Add proper error checking to C++ bindings for Python list append and insert. + `#4208 `_ + +* Work-around for Nvidia's CUDA nvcc compiler in versions 11.4.0 - 11.8.0. + `#4220 `_ + +* A workaround for PyPy was added in the ``py::error_already_set`` + implementation, related to PR `#1895 `_ + released with v2.10.0. + `#4079 `_ + +* Fixed compiler errors when C++23 ``std::forward_like`` is available. + `#4136 `_ + +* Properly raise exceptions in contains methods (like when an object in unhashable). + `#4209 `_ + +* Further improve another error in exception handling. + `#4232 `_ + +* ``get_local_internals()`` was made compatible with + ``finalize_interpreter()``, fixing potential freezes during interpreter + finalization. + `#4192 `_ + +Performance and style: + +* Reserve space in set and STL map casters if possible. This will prevent + unnecessary rehashing / resizing by knowing the number of keys ahead of time + for Python to C++ casting. This improvement will greatly speed up the casting + of large unordered maps and sets. + `#4194 `_ + +* GIL RAII scopes are non-copyable to avoid potential bugs. + `#4183 `_ + +* Explicitly default all relevant ctors for pytypes in the ``PYBIND11_OBJECT`` + macros and enforce the clang-tidy checks ``modernize-use-equals-default`` in + macros as well. + `#4017 `_ + +* Optimize iterator advancement in C++ bindings. + `#4237 `_ + +* Use the modern ``PyObject_GenericGetDict`` and ``PyObject_GenericSetDict`` + for handling dynamic attribute dictionaries. + `#4106 `_ + +* Document that users should use ``PYBIND11_NAMESPACE`` instead of using ``pybind11`` when + opening namespaces. Using namespace declarations and namespace qualification + remain the same as ``pybind11``. This is done to ensure consistent symbol + visibility. + `#4098 `_ + +* Mark ``detail::forward_like`` as constexpr. + `#4147 `_ + +* Optimize unpacking_collector when processing ``arg_v`` arguments. + `#4219 `_ + +* Optimize casting C++ object to ``None``. + `#4269 `_ + + +Build system improvements: + +* CMake: revert overwrite behavior, now opt-in with ``PYBIND11_PYTHONLIBS_OVERRWRITE OFF``. + `#4195 `_ + +* Include a pkg-config file when installing pybind11, such as in the Python + package. + `#4077 `_ + +* Avoid stripping debug symbols when ``CMAKE_BUILD_TYPE`` is set to ``DEBUG`` + instead of ``Debug``. + `#4078 `_ + +* Followup to `#3948 `_, fixing vcpkg again. + `#4123 `_ + Version 2.10.0 (Jul 15, 2022) ----------------------------- diff --git a/pybind11/docs/classes.rst b/pybind11/docs/classes.rst index c0c53135b..4f2167dac 100644 --- a/pybind11/docs/classes.rst +++ b/pybind11/docs/classes.rst @@ -58,6 +58,16 @@ interactive Python session demonstrating this example is shown below: Static member functions can be bound in the same way using :func:`class_::def_static`. +.. note:: + + Binding C++ types in unnamed namespaces (also known as anonymous namespaces) + works reliably on many platforms, but not all. The `XFAIL_CONDITION` in + tests/test_unnamed_namespace_a.py encodes the currently known conditions. + For background see `#4319 `_. + If portability is a concern, it is therefore not recommended to bind C++ + types in unnamed namespaces. It will be safest to manually pick unique + namespace names. + Keyword and default arguments ============================= It is possible to specify keyword and default arguments using the syntax @@ -539,3 +549,7 @@ The ``name`` property returns the name of the enum value as a unicode string. ... By default, these are omitted to conserve space. + +.. warning:: + + Contrary to Python customs, enum values from the wrappers should not be compared using ``is``, but with ``==`` (see `#1177 `_ for background). diff --git a/pybind11/docs/compiling.rst b/pybind11/docs/compiling.rst index 2b543be0b..1fd098bec 100644 --- a/pybind11/docs/compiling.rst +++ b/pybind11/docs/compiling.rst @@ -241,7 +241,7 @@ extension module can be created with just a few lines of code: .. code-block:: cmake - cmake_minimum_required(VERSION 3.4...3.18) + cmake_minimum_required(VERSION 3.5...3.26) project(example LANGUAGES CXX) add_subdirectory(pybind11) @@ -261,6 +261,9 @@ PyPI integration, can be found in the [cmake_example]_ repository. .. versionchanged:: 2.6 CMake 3.4+ is required. +.. versionchanged:: 2.11 + CMake 3.5+ is required. + Further information can be found at :doc:`cmake/index`. pybind11_add_module @@ -495,7 +498,7 @@ You can use these targets to build complex applications. For example, the .. code-block:: cmake - cmake_minimum_required(VERSION 3.4) + cmake_minimum_required(VERSION 3.5...3.26) project(example LANGUAGES CXX) find_package(pybind11 REQUIRED) # or add_subdirectory(pybind11) @@ -553,7 +556,7 @@ information about usage in C++, see :doc:`/advanced/embedding`. .. code-block:: cmake - cmake_minimum_required(VERSION 3.4...3.18) + cmake_minimum_required(VERSION 3.5...3.26) project(example LANGUAGES CXX) find_package(pybind11 REQUIRED) # or add_subdirectory(pybind11) diff --git a/pybind11/docs/conf.py b/pybind11/docs/conf.py index 2da6773f4..6e24751e9 100644 --- a/pybind11/docs/conf.py +++ b/pybind11/docs/conf.py @@ -353,12 +353,11 @@ def prepare(app): f.write(contents) -def clean_up(app, exception): +def clean_up(app, exception): # noqa: ARG001 (DIR / "readme.rst").unlink() def setup(app): - # Add hook for building doxygen xml when needed app.connect("builder-inited", generate_doxygen_xml) diff --git a/pybind11/docs/faq.rst b/pybind11/docs/faq.rst index 28498e7df..1eb00efad 100644 --- a/pybind11/docs/faq.rst +++ b/pybind11/docs/faq.rst @@ -284,7 +284,8 @@ There are three possible solutions: COMPONENTS Interpreter Development)`` on modern CMake (3.12+, 3.15+ better, 3.18.2+ best). Pybind11 in these cases uses the new CMake FindPython instead of the old, deprecated search tools, and these modules are much better at - finding the correct Python. + finding the correct Python. If FindPythonLibs/Interp are not available + (CMake 3.27+), then this will be ignored and FindPython will be used. 3. Set ``PYBIND11_NOPYTHON`` to ``TRUE``. Pybind11 will not search for Python. However, you will have to use the target-based system, and do more setup yourself, because it does not know about or include things that depend on diff --git a/pybind11/docs/release.rst b/pybind11/docs/release.rst index e761cdf7a..4950c3b88 100644 --- a/pybind11/docs/release.rst +++ b/pybind11/docs/release.rst @@ -33,10 +33,12 @@ If you don't have nox, you should either use ``pipx run nox`` instead, or use - Run ``nox -s tests_packaging`` to ensure this was done correctly. - Ensure that all the information in ``setup.cfg`` is up-to-date, like supported Python versions. - - Add release date in ``docs/changelog.rst``. - - Check to make sure - `needs-changelog `_ - issues are entered in the changelog (clear the label when done). + - Add release date in ``docs/changelog.rst`` and integrate the output of + ``nox -s make_changelog``. + - Note that the ``make_changelog`` command inspects + `needs changelog `_. + - Manually clear the ``needs changelog`` labels using the GitHub web + interface (very easy: start by clicking the link above). - ``git add`` and ``git commit``, ``git push``. **Ensure CI passes**. (If it fails due to a known flake issue, either ignore or restart CI.) - Add a release branch if this is a new minor version, or update the existing release branch if it is a patch version diff --git a/pybind11/docs/upgrade.rst b/pybind11/docs/upgrade.rst index 6a9db2d08..b13d21f5e 100644 --- a/pybind11/docs/upgrade.rst +++ b/pybind11/docs/upgrade.rst @@ -8,6 +8,20 @@ to a new version. But it goes into more detail. This includes things like deprecated APIs and their replacements, build system changes, general code modernization and other useful information. +.. _upgrade-guide-2.11: + +v2.11 +===== + +* The minimum version of CMake is now 3.5. A future version will likely move to + requiring something like CMake 3.15. Note that CMake 3.27 is removing the + long-deprecated support for ``FindPythonInterp`` if you set 3.27 as the + minimum or maximum supported version. To prepare for that future, CMake 3.15+ + using ``FindPython`` or setting ``PYBIND11_FINDPYTHON`` is highly recommended, + otherwise pybind11 will automatically switch to using ``FindPython`` if + ``FindPythonInterp`` is not available. + + .. _upgrade-guide-2.9: v2.9 diff --git a/pybind11/include/pybind11/attr.h b/pybind11/include/pybind11/attr.h index db7cd8eff..1044db94d 100644 --- a/pybind11/include/pybind11/attr.h +++ b/pybind11/include/pybind11/attr.h @@ -26,6 +26,9 @@ struct is_method { explicit is_method(const handle &c) : class_(c) {} }; +/// Annotation for setters +struct is_setter {}; + /// Annotation for operators struct is_operator {}; @@ -188,8 +191,8 @@ struct argument_record { struct function_record { function_record() : is_constructor(false), is_new_style_constructor(false), is_stateless(false), - is_operator(false), is_method(false), has_args(false), has_kwargs(false), - prepend(false) {} + is_operator(false), is_method(false), is_setter(false), has_args(false), + has_kwargs(false), prepend(false) {} /// Function name char *name = nullptr; /* why no C++ strings? They generate heavier code.. */ @@ -230,6 +233,9 @@ struct function_record { /// True if this is a method bool is_method : 1; + /// True if this is a setter + bool is_setter : 1; + /// True if the function has a '*args' argument bool has_args : 1; @@ -399,7 +405,7 @@ struct process_attribute : process_attribute_default { template <> struct process_attribute : process_attribute_default { static void init(const char *d, function_record *r) { r->doc = const_cast(d); } - static void init(const char *d, type_record *r) { r->doc = const_cast(d); } + static void init(const char *d, type_record *r) { r->doc = d; } }; template <> struct process_attribute : process_attribute {}; @@ -426,6 +432,12 @@ struct process_attribute : process_attribute_default { } }; +/// Process an attribute which indicates that this function is a setter +template <> +struct process_attribute : process_attribute_default { + static void init(const is_setter &, function_record *r) { r->is_setter = true; } +}; + /// Process an attribute which indicates the parent scope of a method template <> struct process_attribute : process_attribute_default { diff --git a/pybind11/include/pybind11/buffer_info.h b/pybind11/include/pybind11/buffer_info.h index 06120d556..b99ee8bef 100644 --- a/pybind11/include/pybind11/buffer_info.h +++ b/pybind11/include/pybind11/buffer_info.h @@ -37,6 +37,9 @@ inline std::vector f_strides(const std::vector &shape, ssize_t return strides; } +template +struct compare_buffer_info; + PYBIND11_NAMESPACE_END(detail) /// Information record describing a Python buffer object @@ -150,6 +153,17 @@ struct buffer_info { Py_buffer *view() const { return m_view; } Py_buffer *&view() { return m_view; } + /* True if the buffer item type is equivalent to `T`. */ + // To define "equivalent" by example: + // `buffer_info::item_type_is_equivalent_to(b)` and + // `buffer_info::item_type_is_equivalent_to(b)` may both be true + // on some platforms, but `int` and `unsigned` will never be equivalent. + // For the ground truth, please inspect `detail::compare_buffer_info<>`. + template + bool item_type_is_equivalent_to() const { + return detail::compare_buffer_info::compare(*this); + } + private: struct private_ctr_tag {}; @@ -170,9 +184,10 @@ private: PYBIND11_NAMESPACE_BEGIN(detail) -template +template struct compare_buffer_info { static bool compare(const buffer_info &b) { + // NOLINTNEXTLINE(bugprone-sizeof-expression) Needed for `PyObject *` return b.format == format_descriptor::format() && b.itemsize == (ssize_t) sizeof(T); } }; diff --git a/pybind11/include/pybind11/cast.h b/pybind11/include/pybind11/cast.h index a0e32281b..db3934118 100644 --- a/pybind11/include/pybind11/cast.h +++ b/pybind11/include/pybind11/cast.h @@ -29,6 +29,9 @@ #include PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE) + +PYBIND11_WARNING_DISABLE_MSVC(4127) + PYBIND11_NAMESPACE_BEGIN(detail) template @@ -88,7 +91,8 @@ public: template >::value, \ - int> = 0> \ + int> \ + = 0> \ static ::pybind11::handle cast( \ T_ *src, ::pybind11::return_value_policy policy, ::pybind11::handle parent) { \ if (!src) \ @@ -248,7 +252,7 @@ public: return false; } static handle cast(T, return_value_policy /* policy */, handle /* parent */) { - return none().inc_ref(); + return none().release(); } PYBIND11_TYPE_CASTER(T, const_name("None")); }; @@ -291,7 +295,7 @@ public: if (ptr) { return capsule(ptr).release(); } - return none().inc_ref(); + return none().release(); } template @@ -389,7 +393,7 @@ struct string_caster { // For UTF-8 we avoid the need for a temporary `bytes` object by using // `PyUnicode_AsUTF8AndSize`. - if (PYBIND11_SILENCE_MSVC_C4127(UTF_N == 8)) { + if (UTF_N == 8) { Py_ssize_t size = -1; const auto *buffer = reinterpret_cast(PyUnicode_AsUTF8AndSize(load_src.ptr(), &size)); @@ -416,7 +420,7 @@ struct string_caster { = reinterpret_cast(PYBIND11_BYTES_AS_STRING(utfNbytes.ptr())); size_t length = (size_t) PYBIND11_BYTES_SIZE(utfNbytes.ptr()) / sizeof(CharT); // Skip BOM for UTF-16/32 - if (PYBIND11_SILENCE_MSVC_C4127(UTF_N > 8)) { + if (UTF_N > 8) { buffer++; length--; } @@ -537,7 +541,7 @@ public: static handle cast(const CharT *src, return_value_policy policy, handle parent) { if (src == nullptr) { - return pybind11::none().inc_ref(); + return pybind11::none().release(); } return StringCaster::cast(StringType(src), policy, parent); } @@ -572,7 +576,7 @@ public: // figure out how long the first encoded character is in bytes to distinguish between these // two errors. We also allow want to allow unicode characters U+0080 through U+00FF, as // those can fit into a single char value. - if (PYBIND11_SILENCE_MSVC_C4127(StringCaster::UTF_N == 8) && str_len > 1 && str_len <= 4) { + if (StringCaster::UTF_N == 8 && str_len > 1 && str_len <= 4) { auto v0 = static_cast(value[0]); // low bits only: 0-127 // 0b110xxxxx - start of 2-byte sequence @@ -598,7 +602,7 @@ public: // UTF-16 is much easier: we can only have a surrogate pair for values above U+FFFF, thus a // surrogate pair with total length 2 instantly indicates a range error (but not a "your // string was too long" error). - else if (PYBIND11_SILENCE_MSVC_C4127(StringCaster::UTF_N == 16) && str_len == 2) { + else if (StringCaster::UTF_N == 16 && str_len == 2) { one_char = static_cast(value[0]); if (one_char >= 0xD800 && one_char < 0xE000) { throw value_error("Character code point not in range(0x10000)"); @@ -960,7 +964,7 @@ struct move_always< enable_if_t< all_of, negation>, - std::is_move_constructible, + is_move_constructible, std::is_same>().operator T &()), T &>>::value>> : std::true_type {}; template @@ -971,7 +975,7 @@ struct move_if_unreferenced< enable_if_t< all_of, negation>, - std::is_move_constructible, + is_move_constructible, std::is_same>().operator T &()), T &>>::value>> : std::true_type {}; template @@ -1013,11 +1017,14 @@ type_caster &load_type(type_caster &conv, const handle &ha "Internal error: type_caster should only be used for C++ types"); if (!conv.load(handle, true)) { #if !defined(PYBIND11_DETAILED_ERROR_MESSAGES) - throw cast_error("Unable to cast Python instance to C++ type (#define " - "PYBIND11_DETAILED_ERROR_MESSAGES or compile in debug mode for details)"); + throw cast_error( + "Unable to cast Python instance of type " + + str(type::handle_of(handle)).cast() + + " to C++ type '?' (#define " + "PYBIND11_DETAILED_ERROR_MESSAGES or compile in debug mode for details)"); #else throw cast_error("Unable to cast Python instance of type " - + (std::string) str(type::handle_of(handle)) + " to C++ type '" + + str(type::handle_of(handle)).cast() + " to C++ type '" + type_id() + "'"); #endif } @@ -1034,7 +1041,11 @@ make_caster load_type(const handle &handle) { PYBIND11_NAMESPACE_END(detail) // pytype -> C++ type -template ::value, int> = 0> +template ::value + && !detail::is_same_ignoring_cvref::value, + int> + = 0> T cast(const handle &handle) { using namespace detail; static_assert(!cast_is_temporary_value_reference::value, @@ -1048,6 +1059,34 @@ T cast(const handle &handle) { return T(reinterpret_borrow(handle)); } +// Note that `cast(obj)` increments the reference count of `obj`. +// This is necessary for the case that `obj` is a temporary, and could +// not possibly be different, given +// 1. the established convention that the passed `handle` is borrowed, and +// 2. we don't want to force all generic code using `cast()` to special-case +// handling of `T` = `PyObject *` (to increment the reference count there). +// It is the responsibility of the caller to ensure that the reference count +// is decremented. +template ::value + && detail::is_same_ignoring_cvref::value, + int> + = 0> +T cast(Handle &&handle) { + return handle.inc_ref().ptr(); +} +// To optimize way an inc_ref/dec_ref cycle: +template ::value + && detail::is_same_ignoring_cvref::value, + int> + = 0> +T cast(Object &&obj) { + return obj.release().ptr(); +} + // C++ type -> py::object template ::value, int> = 0> object cast(T &&value, @@ -1081,12 +1120,13 @@ detail::enable_if_t::value, T> move(object &&obj) { if (obj.ref_count() > 1) { #if !defined(PYBIND11_DETAILED_ERROR_MESSAGES) throw cast_error( - "Unable to cast Python instance to C++ rvalue: instance has multiple references" - " (#define PYBIND11_DETAILED_ERROR_MESSAGES or compile in debug mode for details)"); + "Unable to cast Python " + str(type::handle_of(obj)).cast() + + " instance to C++ rvalue: instance has multiple references" + " (#define PYBIND11_DETAILED_ERROR_MESSAGES or compile in debug mode for details)"); #else - throw cast_error("Unable to move from Python " + (std::string) str(type::handle_of(obj)) - + " instance to C++ " + type_id() - + " instance: instance has multiple references"); + throw cast_error("Unable to move from Python " + + str(type::handle_of(obj)).cast() + " instance to C++ " + + type_id() + " instance: instance has multiple references"); #endif } @@ -1179,11 +1219,9 @@ enable_if_t::value, T> cast_safe(object &&) pybind11_fail("Internal error: cast_safe fallback invoked"); } template -enable_if_t>::value, void> cast_safe(object &&) {} +enable_if_t::value, void> cast_safe(object &&) {} template -enable_if_t, - std::is_same>>::value, - T> +enable_if_t, std::is_void>::value, T> cast_safe(object &&o) { return pybind11::cast(std::move(o)); } @@ -1193,9 +1231,10 @@ PYBIND11_NAMESPACE_END(detail) // The overloads could coexist, i.e. the #if is not strictly speaking needed, // but it is an easy minor optimization. #if !defined(PYBIND11_DETAILED_ERROR_MESSAGES) -inline cast_error cast_error_unable_to_convert_call_arg() { - return cast_error("Unable to convert call argument to Python object (#define " - "PYBIND11_DETAILED_ERROR_MESSAGES or compile in debug mode for details)"); +inline cast_error cast_error_unable_to_convert_call_arg(const std::string &name) { + return cast_error("Unable to convert call argument '" + name + + "' to Python object (#define " + "PYBIND11_DETAILED_ERROR_MESSAGES or compile in debug mode for details)"); } #else inline cast_error cast_error_unable_to_convert_call_arg(const std::string &name, @@ -1218,7 +1257,7 @@ tuple make_tuple(Args &&...args_) { for (size_t i = 0; i < args.size(); i++) { if (!args[i]) { #if !defined(PYBIND11_DETAILED_ERROR_MESSAGES) - throw cast_error_unable_to_convert_call_arg(); + throw cast_error_unable_to_convert_call_arg(std::to_string(i)); #else std::array argtypes{{type_id()...}}; throw cast_error_unable_to_convert_call_arg(std::to_string(i), argtypes[i]); @@ -1508,7 +1547,7 @@ private: detail::make_caster::cast(std::forward(x), policy, {})); if (!o) { #if !defined(PYBIND11_DETAILED_ERROR_MESSAGES) - throw cast_error_unable_to_convert_call_arg(); + throw cast_error_unable_to_convert_call_arg(std::to_string(args_list.size())); #else throw cast_error_unable_to_convert_call_arg(std::to_string(args_list.size()), type_id()); @@ -1540,12 +1579,12 @@ private: } if (!a.value) { #if !defined(PYBIND11_DETAILED_ERROR_MESSAGES) - throw cast_error_unable_to_convert_call_arg(); + throw cast_error_unable_to_convert_call_arg(a.name); #else throw cast_error_unable_to_convert_call_arg(a.name, a.type); #endif } - m_kwargs[a.name] = a.value; + m_kwargs[a.name] = std::move(a.value); } void process(list & /*args_list*/, detail::kwargs_proxy kp) { diff --git a/pybind11/include/pybind11/detail/class.h b/pybind11/include/pybind11/detail/class.h index 42720f844..bc2b40c50 100644 --- a/pybind11/include/pybind11/detail/class.h +++ b/pybind11/include/pybind11/detail/class.h @@ -55,6 +55,9 @@ extern "C" inline int pybind11_static_set(PyObject *self, PyObject *obj, PyObjec return PyProperty_Type.tp_descr_set(self, cls, value); } +// Forward declaration to use in `make_static_property_type()` +inline void enable_dynamic_attributes(PyHeapTypeObject *heap_type); + /** A `static_property` is the same as a `property` but the `__get__()` and `__set__()` methods are modified to always use the object type instead of a concrete instance. Return value: New reference. */ @@ -87,6 +90,13 @@ inline PyTypeObject *make_static_property_type() { pybind11_fail("make_static_property_type(): failure in PyType_Ready()!"); } +# if PY_VERSION_HEX >= 0x030C0000 + // PRE 3.12 FEATURE FREEZE. PLEASE REVIEW AFTER FREEZE. + // Since Python-3.12 property-derived types are required to + // have dynamic attributes (to set `__doc__`) + enable_dynamic_attributes(heap_type); +# endif + setattr((PyObject *) type, "__module__", str("pybind11_builtins")); PYBIND11_SET_OLDPY_QUALNAME(type, name_obj); @@ -435,9 +445,17 @@ inline void clear_instance(PyObject *self) { /// Instance destructor function for all pybind11 types. It calls `type_info.dealloc` /// to destroy the C++ object itself, while the rest is Python bookkeeping. extern "C" inline void pybind11_object_dealloc(PyObject *self) { + auto *type = Py_TYPE(self); + + // If this is a GC tracked object, untrack it first + // Note that the track call is implicitly done by the + // default tp_alloc, which we never override. + if (PyType_HasFeature(type, Py_TPFLAGS_HAVE_GC) != 0) { + PyObject_GC_UnTrack(self); + } + clear_instance(self); - auto *type = Py_TYPE(self); type->tp_free(self); #if PY_VERSION_HEX < 0x03080000 @@ -502,31 +520,6 @@ inline PyObject *make_object_base_type(PyTypeObject *metaclass) { return (PyObject *) heap_type; } -/// dynamic_attr: Support for `d = instance.__dict__`. -extern "C" inline PyObject *pybind11_get_dict(PyObject *self, void *) { - PyObject *&dict = *_PyObject_GetDictPtr(self); - if (!dict) { - dict = PyDict_New(); - } - Py_XINCREF(dict); - return dict; -} - -/// dynamic_attr: Support for `instance.__dict__ = dict()`. -extern "C" inline int pybind11_set_dict(PyObject *self, PyObject *new_dict, void *) { - if (!PyDict_Check(new_dict)) { - PyErr_Format(PyExc_TypeError, - "__dict__ must be set to a dictionary, not a '%.200s'", - get_fully_qualified_tp_name(Py_TYPE(new_dict)).c_str()); - return -1; - } - PyObject *&dict = *_PyObject_GetDictPtr(self); - Py_INCREF(new_dict); - Py_CLEAR(dict); - dict = new_dict; - return 0; -} - /// dynamic_attr: Allow the garbage collector to traverse the internal instance `__dict__`. extern "C" inline int pybind11_traverse(PyObject *self, visitproc visit, void *arg) { PyObject *&dict = *_PyObject_GetDictPtr(self); @@ -558,9 +551,17 @@ inline void enable_dynamic_attributes(PyHeapTypeObject *heap_type) { type->tp_traverse = pybind11_traverse; type->tp_clear = pybind11_clear; - static PyGetSetDef getset[] = { - {const_cast("__dict__"), pybind11_get_dict, pybind11_set_dict, nullptr, nullptr}, - {nullptr, nullptr, nullptr, nullptr, nullptr}}; + static PyGetSetDef getset[] = {{ +#if PY_VERSION_HEX < 0x03070000 + const_cast("__dict__"), +#else + "__dict__", +#endif + PyObject_GenericGetDict, + PyObject_GenericSetDict, + nullptr, + nullptr}, + {nullptr, nullptr, nullptr, nullptr, nullptr}}; type->tp_getset = getset; } diff --git a/pybind11/include/pybind11/detail/common.h b/pybind11/include/pybind11/detail/common.h index 1da323f31..31a54c773 100644 --- a/pybind11/include/pybind11/detail/common.h +++ b/pybind11/include/pybind11/detail/common.h @@ -10,15 +10,76 @@ #pragma once #define PYBIND11_VERSION_MAJOR 2 -#define PYBIND11_VERSION_MINOR 10 -#define PYBIND11_VERSION_PATCH 0 +#define PYBIND11_VERSION_MINOR 11 +#define PYBIND11_VERSION_PATCH 1 // Similar to Python's convention: https://docs.python.org/3/c-api/apiabiversion.html // Additional convention: 0xD = dev -#define PYBIND11_VERSION_HEX 0x020A0000 +#define PYBIND11_VERSION_HEX 0x020B0100 -#define PYBIND11_NAMESPACE_BEGIN(name) namespace name { -#define PYBIND11_NAMESPACE_END(name) } +// Define some generic pybind11 helper macros for warning management. +// +// Note that compiler-specific push/pop pairs are baked into the +// PYBIND11_NAMESPACE_BEGIN/PYBIND11_NAMESPACE_END pair of macros. Therefore manual +// PYBIND11_WARNING_PUSH/PYBIND11_WARNING_POP are usually only needed in `#include` sections. +// +// If you find you need to suppress a warning, please try to make the suppression as local as +// possible using these macros. Please also be sure to push/pop with the pybind11 macros. Please +// only use compiler specifics if you need to check specific versions, e.g. Apple Clang vs. vanilla +// Clang. +#if defined(_MSC_VER) +# define PYBIND11_COMPILER_MSVC +# define PYBIND11_PRAGMA(...) __pragma(__VA_ARGS__) +# define PYBIND11_WARNING_PUSH PYBIND11_PRAGMA(warning(push)) +# define PYBIND11_WARNING_POP PYBIND11_PRAGMA(warning(pop)) +#elif defined(__INTEL_COMPILER) +# define PYBIND11_COMPILER_INTEL +# define PYBIND11_PRAGMA(...) _Pragma(#__VA_ARGS__) +# define PYBIND11_WARNING_PUSH PYBIND11_PRAGMA(warning push) +# define PYBIND11_WARNING_POP PYBIND11_PRAGMA(warning pop) +#elif defined(__clang__) +# define PYBIND11_COMPILER_CLANG +# define PYBIND11_PRAGMA(...) _Pragma(#__VA_ARGS__) +# define PYBIND11_WARNING_PUSH PYBIND11_PRAGMA(clang diagnostic push) +# define PYBIND11_WARNING_POP PYBIND11_PRAGMA(clang diagnostic push) +#elif defined(__GNUC__) +# define PYBIND11_COMPILER_GCC +# define PYBIND11_PRAGMA(...) _Pragma(#__VA_ARGS__) +# define PYBIND11_WARNING_PUSH PYBIND11_PRAGMA(GCC diagnostic push) +# define PYBIND11_WARNING_POP PYBIND11_PRAGMA(GCC diagnostic pop) +#endif + +#ifdef PYBIND11_COMPILER_MSVC +# define PYBIND11_WARNING_DISABLE_MSVC(name) PYBIND11_PRAGMA(warning(disable : name)) +#else +# define PYBIND11_WARNING_DISABLE_MSVC(name) +#endif + +#ifdef PYBIND11_COMPILER_CLANG +# define PYBIND11_WARNING_DISABLE_CLANG(name) PYBIND11_PRAGMA(clang diagnostic ignored name) +#else +# define PYBIND11_WARNING_DISABLE_CLANG(name) +#endif + +#ifdef PYBIND11_COMPILER_GCC +# define PYBIND11_WARNING_DISABLE_GCC(name) PYBIND11_PRAGMA(GCC diagnostic ignored name) +#else +# define PYBIND11_WARNING_DISABLE_GCC(name) +#endif + +#ifdef PYBIND11_COMPILER_INTEL +# define PYBIND11_WARNING_DISABLE_INTEL(name) PYBIND11_PRAGMA(warning disable name) +#else +# define PYBIND11_WARNING_DISABLE_INTEL(name) +#endif + +#define PYBIND11_NAMESPACE_BEGIN(name) \ + namespace name { \ + PYBIND11_WARNING_PUSH + +#define PYBIND11_NAMESPACE_END(name) \ + PYBIND11_WARNING_POP \ + } // Robust support for some features and loading modules compiled against different pybind versions // requires forcing hidden visibility on pybind code, so we enforce this by setting the attribute @@ -96,13 +157,10 @@ #endif #if !defined(PYBIND11_EXPORT_EXCEPTION) -# ifdef __MINGW32__ -// workaround for: -// error: 'dllexport' implies default visibility, but xxx has already been declared with a -// different visibility -# define PYBIND11_EXPORT_EXCEPTION -# else +# if defined(__apple_build_version__) # define PYBIND11_EXPORT_EXCEPTION PYBIND11_EXPORT +# else +# define PYBIND11_EXPORT_EXCEPTION # endif #endif @@ -154,9 +212,9 @@ /// Include Python header, disable linking to pythonX_d.lib on Windows in debug mode #if defined(_MSC_VER) -# pragma warning(push) +PYBIND11_WARNING_PUSH +PYBIND11_WARNING_DISABLE_MSVC(4505) // C4505: 'PySlice_GetIndicesEx': unreferenced local function has been removed (PyPy only) -# pragma warning(disable : 4505) # if defined(_DEBUG) && !defined(Py_DEBUG) // Workaround for a VS 2022 issue. // NOTE: This workaround knowingly violates the Python.h include order requirement: @@ -205,11 +263,8 @@ # endif #endif -#if defined(__cpp_lib_char8_t) && __cpp_lib_char8_t >= 201811L -# define PYBIND11_HAS_U8STRING -#endif - #include +// Reminder: WITH_THREAD is always defined if PY_VERSION_HEX >= 0x03070000 #if PY_VERSION_HEX < 0x03060000 # error "PYTHON < 3.6 IS UNSUPPORTED. pybind11 v2.9 was the last to support Python 2 and 3.5." #endif @@ -233,12 +288,16 @@ # undef copysign #endif +#if defined(PYPY_VERSION) && !defined(PYBIND11_SIMPLE_GIL_MANAGEMENT) +# define PYBIND11_SIMPLE_GIL_MANAGEMENT +#endif + #if defined(_MSC_VER) # if defined(PYBIND11_DEBUG_MARKER) # define _DEBUG # undef PYBIND11_DEBUG_MARKER # endif -# pragma warning(pop) +PYBIND11_WARNING_POP #endif #include @@ -259,6 +318,17 @@ # endif #endif +// Must be after including or one of the other headers specified by the standard +#if defined(__cpp_lib_char8_t) && __cpp_lib_char8_t >= 201811L +# define PYBIND11_HAS_U8STRING +#endif + +// See description of PR #4246: +#if !defined(PYBIND11_NO_ASSERT_GIL_HELD_INCREF_DECREF) && !defined(NDEBUG) \ + && !defined(PYPY_VERSION) && !defined(PYBIND11_ASSERT_GIL_HELD_INCREF_DECREF) +# define PYBIND11_ASSERT_GIL_HELD_INCREF_DECREF +#endif + // #define PYBIND11_STR_LEGACY_PERMISSIVE // If DEFINED, pybind11::str can hold PyUnicodeObject or PyBytesObject // (probably surprising and never documented, but this was the @@ -363,7 +433,7 @@ /** \rst This macro creates the entry point that will be invoked when the Python interpreter - imports an extension module. The module name is given as the fist argument and it + imports an extension module. The module name is given as the first argument and it should not be in quotes. The second macro argument defines a variable of type `py::module_` which can be used to initialize the module. @@ -588,6 +658,10 @@ template using remove_cvref_t = typename remove_cvref::type; #endif +/// Example usage: is_same_ignoring_cvref::value +template +using is_same_ignoring_cvref = std::is_same, U>; + /// Index sequences #if defined(PYBIND11_CPP14) using std::index_sequence; @@ -681,7 +755,16 @@ template struct remove_class { using type = R(A...); }; - +#ifdef __cpp_noexcept_function_type +template +struct remove_class { + using type = R(A...); +}; +template +struct remove_class { + using type = R(A...); +}; +#endif /// Helper template to strip away type modifiers template struct intrinsic_type { @@ -898,12 +981,6 @@ using expand_side_effects = bool[]; PYBIND11_NAMESPACE_END(detail) -#if defined(_MSC_VER) -# pragma warning(push) -# pragma warning(disable : 4275) -// warning C4275: An exported class was derived from a class that wasn't exported. -// Can be ignored when derived from a STL class. -#endif /// C++ bindings of builtin Python exceptions class PYBIND11_EXPORT_EXCEPTION builtin_exception : public std::runtime_error { public: @@ -911,9 +988,6 @@ public: /// Set the error using the Python C API virtual void set_error() const = 0; }; -#if defined(_MSC_VER) -# pragma warning(pop) -#endif #define PYBIND11_RUNTIME_EXCEPTION(name, type) \ class PYBIND11_EXPORT_EXCEPTION name : public builtin_exception { \ @@ -948,6 +1022,15 @@ PYBIND11_RUNTIME_EXCEPTION(reference_cast_error, PyExc_RuntimeError) /// Used in template struct format_descriptor {}; +template +struct format_descriptor< + T, + detail::enable_if_t::value>> { + static constexpr const char c = 'O'; + static constexpr const char value[2] = {c, '\0'}; + static std::string format() { return std::string(1, c); } +}; + PYBIND11_NAMESPACE_BEGIN(detail) // Returns the index of the given type in the type char array below, and in the list in numpy.h // The order here is: bool; 8 ints ((signed,unsigned)x(8,16,32,64)bits); float,double,long double; @@ -1033,12 +1116,7 @@ PYBIND11_NAMESPACE_END(detail) /// - regular: static_cast(&Class::func) /// - sweet: overload_cast(&Class::func) template -# if (defined(_MSC_VER) && _MSC_VER < 1920) /* MSVC 2017 */ \ - || (defined(__clang__) && __clang_major__ == 5) -static constexpr detail::overload_cast_impl overload_cast = {}; -# else -static constexpr detail::overload_cast_impl overload_cast; -# endif +static constexpr detail::overload_cast_impl overload_cast{}; #endif /// Const member function selector for overload_cast @@ -1147,20 +1225,28 @@ constexpr # define PYBIND11_WORKAROUND_INCORRECT_GCC_UNUSED_BUT_SET_PARAMETER(...) #endif -#if defined(_MSC_VER) // All versions (as of July 2021). - -// warning C4127: Conditional expression is constant -constexpr inline bool silence_msvc_c4127(bool cond) { return cond; } - -# define PYBIND11_SILENCE_MSVC_C4127(...) ::pybind11::detail::silence_msvc_c4127(__VA_ARGS__) - -#else -# define PYBIND11_SILENCE_MSVC_C4127(...) __VA_ARGS__ +#if defined(__clang__) \ + && (defined(__apple_build_version__) /* AppleClang 13.0.0.13000029 was the only data point \ + available. */ \ + || (__clang_major__ >= 7 \ + && __clang_major__ <= 12) /* Clang 3, 5, 13, 14, 15 do not generate the warning. */ \ + ) +# define PYBIND11_DETECTED_CLANG_WITH_MISLEADING_CALL_STD_MOVE_EXPLICITLY_WARNING +// Example: +// tests/test_kwargs_and_defaults.cpp:46:68: error: local variable 'args' will be copied despite +// being returned by name [-Werror,-Wreturn-std-move] +// m.def("args_function", [](py::args args) -> py::tuple { return args; }); +// ^~~~ +// test_kwargs_and_defaults.cpp:46:68: note: call 'std::move' explicitly to avoid copying +// m.def("args_function", [](py::args args) -> py::tuple { return args; }); +// ^~~~ +// std::move(args) #endif // Pybind offers detailed error messages by default for all builts that are debug (through the -// negation of ndebug). This can also be manually enabled by users, for any builds, through -// defining PYBIND11_DETAILED_ERROR_MESSAGES. +// negation of NDEBUG). This can also be manually enabled by users, for any builds, through +// defining PYBIND11_DETAILED_ERROR_MESSAGES. This information is primarily useful for those +// who are writing (as opposed to merely using) libraries that use pybind11. #if !defined(PYBIND11_DETAILED_ERROR_MESSAGES) && !defined(NDEBUG) # define PYBIND11_DETAILED_ERROR_MESSAGES #endif diff --git a/pybind11/include/pybind11/detail/descr.h b/pybind11/include/pybind11/detail/descr.h index e7a5e2c14..635614b0d 100644 --- a/pybind11/include/pybind11/detail/descr.h +++ b/pybind11/include/pybind11/detail/descr.h @@ -143,11 +143,24 @@ constexpr descr concat(const descr &descr) { return descr; } +#ifdef __cpp_fold_expressions +template +constexpr descr operator,(const descr &a, + const descr &b) { + return a + const_name(", ") + b; +} + +template +constexpr auto concat(const descr &d, const Args &...args) { + return (d, ..., args); +} +#else template constexpr auto concat(const descr &d, const Args &...args) -> decltype(std::declval>() + concat(args...)) { return d + const_name(", ") + concat(args...); } +#endif template constexpr descr type_descr(const descr &descr) { diff --git a/pybind11/include/pybind11/detail/init.h b/pybind11/include/pybind11/detail/init.h index 05f4fe54a..e21171688 100644 --- a/pybind11/include/pybind11/detail/init.h +++ b/pybind11/include/pybind11/detail/init.h @@ -12,6 +12,9 @@ #include "class.h" PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE) + +PYBIND11_WARNING_DISABLE_MSVC(4127) + PYBIND11_NAMESPACE_BEGIN(detail) template <> @@ -115,7 +118,7 @@ template void construct(value_and_holder &v_h, Cpp *ptr, bool need_alias) { PYBIND11_WORKAROUND_INCORRECT_MSVC_C4100(need_alias); no_nullptr(ptr); - if (PYBIND11_SILENCE_MSVC_C4127(Class::has_alias) && need_alias && !is_alias(ptr)) { + if (Class::has_alias && need_alias && !is_alias(ptr)) { // We're going to try to construct an alias by moving the cpp type. Whether or not // that succeeds, we still need to destroy the original cpp pointer (either the // moved away leftover, if the alias construction works, or the value itself if we @@ -156,7 +159,7 @@ void construct(value_and_holder &v_h, Holder holder, bool need_alias) { auto *ptr = holder_helper>::get(holder); no_nullptr(ptr); // If we need an alias, check that the held pointer is actually an alias instance - if (PYBIND11_SILENCE_MSVC_C4127(Class::has_alias) && need_alias && !is_alias(ptr)) { + if (Class::has_alias && need_alias && !is_alias(ptr)) { throw type_error("pybind11::init(): construction failed: returned holder-wrapped instance " "is not an alias instance"); } @@ -172,9 +175,9 @@ void construct(value_and_holder &v_h, Holder holder, bool need_alias) { template void construct(value_and_holder &v_h, Cpp &&result, bool need_alias) { PYBIND11_WORKAROUND_INCORRECT_MSVC_C4100(need_alias); - static_assert(std::is_move_constructible>::value, + static_assert(is_move_constructible>::value, "pybind11::init() return-by-value factory function requires a movable class"); - if (PYBIND11_SILENCE_MSVC_C4127(Class::has_alias) && need_alias) { + if (Class::has_alias && need_alias) { construct_alias_from_cpp(is_alias_constructible{}, v_h, std::move(result)); } else { v_h.value_ptr() = new Cpp(std::move(result)); @@ -187,7 +190,7 @@ void construct(value_and_holder &v_h, Cpp &&result, bool need_alias) { template void construct(value_and_holder &v_h, Alias &&result, bool) { static_assert( - std::is_move_constructible>::value, + is_move_constructible>::value, "pybind11::init() return-by-alias-value factory function requires a movable alias class"); v_h.value_ptr() = new Alias(std::move(result)); } @@ -206,10 +209,11 @@ struct constructor { extra...); } - template , Args...>::value, - int> = 0> + template < + typename Class, + typename... Extra, + enable_if_t, Args...>::value, int> + = 0> static void execute(Class &cl, const Extra &...extra) { cl.def( "__init__", @@ -226,10 +230,11 @@ struct constructor { extra...); } - template , Args...>::value, - int> = 0> + template < + typename Class, + typename... Extra, + enable_if_t, Args...>::value, int> + = 0> static void execute(Class &cl, const Extra &...extra) { cl.def( "__init__", @@ -245,10 +250,11 @@ struct constructor { // Implementing class for py::init_alias<...>() template struct alias_constructor { - template , Args...>::value, - int> = 0> + template < + typename Class, + typename... Extra, + enable_if_t, Args...>::value, int> + = 0> static void execute(Class &cl, const Extra &...extra) { cl.def( "__init__", diff --git a/pybind11/include/pybind11/detail/internals.h b/pybind11/include/pybind11/detail/internals.h index 6ca5e1482..aaa7f8686 100644 --- a/pybind11/include/pybind11/detail/internals.h +++ b/pybind11/include/pybind11/detail/internals.h @@ -9,6 +9,12 @@ #pragma once +#include "common.h" + +#if defined(WITH_THREAD) && defined(PYBIND11_SIMPLE_GIL_MANAGEMENT) +# include "../gil.h" +#endif + #include "../pytypes.h" #include @@ -28,15 +34,26 @@ /// further ABI-incompatible changes may be made before the ABI is officially /// changed to the new version. #ifndef PYBIND11_INTERNALS_VERSION -# define PYBIND11_INTERNALS_VERSION 4 +# if PY_VERSION_HEX >= 0x030C0000 +// Version bump for Python 3.12+, before first 3.12 beta release. +# define PYBIND11_INTERNALS_VERSION 5 +# else +# define PYBIND11_INTERNALS_VERSION 4 +# endif #endif +// This requirement is mainly to reduce the support burden (see PR #4570). +static_assert(PY_VERSION_HEX < 0x030C0000 || PYBIND11_INTERNALS_VERSION >= 5, + "pybind11 ABI version 5 is the minimum for Python 3.12+"); + PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE) using ExceptionTranslator = void (*)(std::exception_ptr); PYBIND11_NAMESPACE_BEGIN(detail) +constexpr const char *internals_function_record_capsule_name = "pybind11_function_record_capsule"; + // Forward declarations inline PyTypeObject *make_static_property_type(); inline PyTypeObject *make_default_metaclass(); @@ -49,7 +66,7 @@ inline PyObject *make_object_base_type(PyTypeObject *metaclass); // `Py_LIMITED_API` anyway. # if PYBIND11_INTERNALS_VERSION > 4 # define PYBIND11_TLS_KEY_REF Py_tss_t & -# ifdef __GNUC__ +# if defined(__GNUC__) && !defined(__INTEL_COMPILER) // Clang on macOS warns due to `Py_tss_NEEDS_INIT` not specifying an initializer // for every field. # define PYBIND11_TLS_KEY_INIT(var) \ @@ -106,7 +123,8 @@ inline void tls_replace_value(PYBIND11_TLS_KEY_REF key, void *value) { // libstdc++, this doesn't happen: equality and the type_index hash are based on the type name, // which works. If not under a known-good stl, provide our own name-based hash and equality // functions that use the type name. -#if defined(__GLIBCXX__) +#if (PYBIND11_INTERNALS_VERSION <= 4 && defined(__GLIBCXX__)) \ + || (PYBIND11_INTERNALS_VERSION >= 5 && !defined(_LIBCPP_VERSION)) inline bool same_type(const std::type_info &lhs, const std::type_info &rhs) { return lhs == rhs; } using type_hash = std::hash; using type_equal_to = std::equal_to; @@ -169,11 +187,23 @@ struct internals { PyTypeObject *default_metaclass; PyObject *instance_base; #if defined(WITH_THREAD) + // Unused if PYBIND11_SIMPLE_GIL_MANAGEMENT is defined: PYBIND11_TLS_KEY_INIT(tstate) # if PYBIND11_INTERNALS_VERSION > 4 PYBIND11_TLS_KEY_INIT(loader_life_support_tls_key) # endif // PYBIND11_INTERNALS_VERSION > 4 + // Unused if PYBIND11_SIMPLE_GIL_MANAGEMENT is defined: PyInterpreterState *istate = nullptr; + +# if PYBIND11_INTERNALS_VERSION > 4 + // Note that we have to use a std::string to allocate memory to ensure a unique address + // We want unique addresses since we use pointer equality to compare function records + std::string function_record_capsule_name = internals_function_record_capsule_name; +# endif + + internals() = default; + internals(const internals &other) = delete; + internals &operator=(const internals &other) = delete; ~internals() { # if PYBIND11_INTERNALS_VERSION > 4 PYBIND11_TLS_FREE(loader_life_support_tls_key); @@ -401,6 +431,38 @@ inline void translate_local_exception(std::exception_ptr p) { } #endif +inline object get_python_state_dict() { + object state_dict; +#if PYBIND11_INTERNALS_VERSION <= 4 || PY_VERSION_HEX < 0x03080000 || defined(PYPY_VERSION) + state_dict = reinterpret_borrow(PyEval_GetBuiltins()); +#else +# if PY_VERSION_HEX < 0x03090000 + PyInterpreterState *istate = _PyInterpreterState_Get(); +# else + PyInterpreterState *istate = PyInterpreterState_Get(); +# endif + if (istate) { + state_dict = reinterpret_borrow(PyInterpreterState_GetDict(istate)); + } +#endif + if (!state_dict) { + raise_from(PyExc_SystemError, "pybind11::detail::get_python_state_dict() FAILED"); + } + return state_dict; +} + +inline object get_internals_obj_from_state_dict(handle state_dict) { + return reinterpret_borrow(dict_getitemstring(state_dict.ptr(), PYBIND11_INTERNALS_ID)); +} + +inline internals **get_internals_pp_from_capsule(handle obj) { + void *raw_ptr = PyCapsule_GetPointer(obj.ptr(), /*name=*/nullptr); + if (raw_ptr == nullptr) { + raise_from(PyExc_SystemError, "pybind11::detail::get_internals_pp_from_capsule() FAILED"); + } + return static_cast(raw_ptr); +} + /// Return a reference to the current `internals` data PYBIND11_NOINLINE internals &get_internals() { auto **&internals_pp = get_internals_pp(); @@ -408,21 +470,29 @@ PYBIND11_NOINLINE internals &get_internals() { return **internals_pp; } +#if defined(WITH_THREAD) +# if defined(PYBIND11_SIMPLE_GIL_MANAGEMENT) + gil_scoped_acquire gil; +# else // Ensure that the GIL is held since we will need to make Python calls. // Cannot use py::gil_scoped_acquire here since that constructor calls get_internals. struct gil_scoped_acquire_local { gil_scoped_acquire_local() : state(PyGILState_Ensure()) {} + gil_scoped_acquire_local(const gil_scoped_acquire_local &) = delete; + gil_scoped_acquire_local &operator=(const gil_scoped_acquire_local &) = delete; ~gil_scoped_acquire_local() { PyGILState_Release(state); } const PyGILState_STATE state; } gil; +# endif +#endif error_scope err_scope; - PYBIND11_STR_TYPE id(PYBIND11_INTERNALS_ID); - auto builtins = handle(PyEval_GetBuiltins()); - if (builtins.contains(id) && isinstance(builtins[id])) { - internals_pp = static_cast(capsule(builtins[id])); - - // We loaded builtins through python's builtins, which means that our `error_already_set` + dict state_dict = get_python_state_dict(); + if (object internals_obj = get_internals_obj_from_state_dict(state_dict)) { + internals_pp = get_internals_pp_from_capsule(internals_obj); + } + if (internals_pp && *internals_pp) { + // We loaded the internals through `state_dict`, which means that our `error_already_set` // and `builtin_exception` may be different local classes than the ones set up in the // initial exception translator, below, so add another for our local exception classes. // @@ -440,16 +510,15 @@ PYBIND11_NOINLINE internals &get_internals() { internals_ptr = new internals(); #if defined(WITH_THREAD) -# if PY_VERSION_HEX < 0x03090000 - PyEval_InitThreads(); -# endif PyThreadState *tstate = PyThreadState_Get(); + // NOLINTNEXTLINE(bugprone-assignment-in-if-condition) if (!PYBIND11_TLS_KEY_CREATE(internals_ptr->tstate)) { pybind11_fail("get_internals: could not successfully initialize the tstate TSS key!"); } PYBIND11_TLS_REPLACE_VALUE(internals_ptr->tstate, tstate); # if PYBIND11_INTERNALS_VERSION > 4 + // NOLINTNEXTLINE(bugprone-assignment-in-if-condition) if (!PYBIND11_TLS_KEY_CREATE(internals_ptr->loader_life_support_tls_key)) { pybind11_fail("get_internals: could not successfully initialize the " "loader_life_support TSS key!"); @@ -457,7 +526,7 @@ PYBIND11_NOINLINE internals &get_internals() { # endif internals_ptr->istate = tstate->interp; #endif - builtins[id] = capsule(internals_pp); + state_dict[PYBIND11_INTERNALS_ID] = capsule(internals_pp); internals_ptr->registered_exception_translators.push_front(&translate_exception); internals_ptr->static_property_type = make_static_property_type(); internals_ptr->default_metaclass = make_default_metaclass(); @@ -489,6 +558,7 @@ struct local_internals { struct shared_loader_life_support_data { PYBIND11_TLS_KEY_INIT(loader_life_support_tls_key) shared_loader_life_support_data() { + // NOLINTNEXTLINE(bugprone-assignment-in-if-condition) if (!PYBIND11_TLS_KEY_CREATE(loader_life_support_tls_key)) { pybind11_fail("local_internals: could not successfully initialize the " "loader_life_support TLS key!"); @@ -512,8 +582,13 @@ struct local_internals { /// Works like `get_internals`, but for things which are locally registered. inline local_internals &get_local_internals() { - static local_internals locals; - return locals; + // Current static can be created in the interpreter finalization routine. If the later will be + // destroyed in another static variable destructor, creation of this static there will cause + // static deinitialization fiasco. In order to avoid it we avoid destruction of the + // local_internals static. One can read more about the problem and current solution here: + // https://google.github.io/styleguide/cppguide.html#Static_and_Global_Variables + static auto *locals = new local_internals(); + return *locals; } /// Constructs a std::string with the given arguments, stores it in `internals`, and returns its @@ -527,6 +602,25 @@ const char *c_str(Args &&...args) { return strings.front().c_str(); } +inline const char *get_function_record_capsule_name() { +#if PYBIND11_INTERNALS_VERSION > 4 + return get_internals().function_record_capsule_name.c_str(); +#else + return nullptr; +#endif +} + +// Determine whether or not the following capsule contains a pybind11 function record. +// Note that we use `internals` to make sure that only ABI compatible records are touched. +// +// This check is currently used in two places: +// - An important optimization in functional.h to avoid overhead in C++ -> Python -> C++ +// - The sibling feature of cpp_function to allow overloads +inline bool is_function_record_capsule(const capsule &cap) { + // Pointer equality as we rely on internals() to ensure unique pointers + return cap.name() == get_function_record_capsule_name(); +} + PYBIND11_NAMESPACE_END(detail) /// Returns a named pointer that is shared among all extension modules (using the same diff --git a/pybind11/include/pybind11/detail/type_caster_base.h b/pybind11/include/pybind11/detail/type_caster_base.h index 21f69c289..16387506c 100644 --- a/pybind11/include/pybind11/detail/type_caster_base.h +++ b/pybind11/include/pybind11/detail/type_caster_base.h @@ -258,9 +258,9 @@ struct value_and_holder { // Main constructor for a found value/holder: value_and_holder(instance *i, const detail::type_info *type, size_t vpos, size_t index) - : inst{i}, index{index}, type{type}, vh{inst->simple_layout - ? inst->simple_value_holder - : &inst->nonsimple.values_and_holders[vpos]} {} + : inst{i}, index{index}, type{type}, + vh{inst->simple_layout ? inst->simple_value_holder + : &inst->nonsimple.values_and_holders[vpos]} {} // Default constructor (used to signal a value-and-holder not found by get_value_and_holder()) value_and_holder() = default; @@ -822,23 +822,179 @@ using movable_cast_op_type typename std::add_rvalue_reference>::type, typename std::add_lvalue_reference>::type>>; +// Does the container have a mapped type and is it recursive? +// Implemented by specializations below. +template +struct container_mapped_type_traits { + static constexpr bool has_mapped_type = false; + static constexpr bool has_recursive_mapped_type = false; +}; + +template +struct container_mapped_type_traits< + Container, + typename std::enable_if< + std::is_same::value>::type> { + static constexpr bool has_mapped_type = true; + static constexpr bool has_recursive_mapped_type = true; +}; + +template +struct container_mapped_type_traits< + Container, + typename std::enable_if< + negation>::value>::type> { + static constexpr bool has_mapped_type = true; + static constexpr bool has_recursive_mapped_type = false; +}; + +// Does the container have a value type and is it recursive? +// Implemented by specializations below. +template +struct container_value_type_traits : std::false_type { + static constexpr bool has_value_type = false; + static constexpr bool has_recursive_value_type = false; +}; + +template +struct container_value_type_traits< + Container, + typename std::enable_if< + std::is_same::value>::type> { + static constexpr bool has_value_type = true; + static constexpr bool has_recursive_value_type = true; +}; + +template +struct container_value_type_traits< + Container, + typename std::enable_if< + negation>::value>::type> { + static constexpr bool has_value_type = true; + static constexpr bool has_recursive_value_type = false; +}; + +/* + * Tag to be used for representing the bottom of recursively defined types. + * Define this tag so we don't have to use void. + */ +struct recursive_bottom {}; + +/* + * Implementation detail of `recursive_container_traits` below. + * `T` is the `value_type` of the container, which might need to be modified to + * avoid recursive types and const types. + */ +template +struct impl_type_to_check_recursively { + /* + * If the container is recursive, then no further recursion should be done. + */ + using if_recursive = recursive_bottom; + /* + * Otherwise yield `T` unchanged. + */ + using if_not_recursive = T; +}; + +/* + * For pairs - only as value type of a map -, the first type should remove the `const`. + * Also, if the map is recursive, then the recursive checking should consider + * the first type only. + */ +template +struct impl_type_to_check_recursively, /* is_this_a_map = */ true> { + using if_recursive = typename std::remove_const::type; + using if_not_recursive = std::pair::type, B>; +}; + +/* + * Implementation of `recursive_container_traits` below. + */ +template +struct impl_recursive_container_traits { + using type_to_check_recursively = recursive_bottom; +}; + +template +struct impl_recursive_container_traits< + Container, + typename std::enable_if::has_value_type>::type> { + static constexpr bool is_recursive + = container_mapped_type_traits::has_recursive_mapped_type + || container_value_type_traits::has_recursive_value_type; + /* + * This member dictates which type Pybind11 should check recursively in traits + * such as `is_move_constructible`, `is_copy_constructible`, `is_move_assignable`, ... + * Direct access to `value_type` should be avoided: + * 1. `value_type` might recursively contain the type again + * 2. `value_type` of STL map types is `std::pair`, the `const` + * should be removed. + * + */ + using type_to_check_recursively = typename std::conditional< + is_recursive, + typename impl_type_to_check_recursively< + typename Container::value_type, + container_mapped_type_traits::has_mapped_type>::if_recursive, + typename impl_type_to_check_recursively< + typename Container::value_type, + container_mapped_type_traits::has_mapped_type>::if_not_recursive>::type; +}; + +/* + * This trait defines the `type_to_check_recursively` which is needed to properly + * handle recursively defined traits such as `is_move_constructible` without going + * into an infinite recursion. + * Should be used instead of directly accessing the `value_type`. + * It cancels the recursion by returning the `recursive_bottom` tag. + * + * The default definition of `type_to_check_recursively` is as follows: + * + * 1. By default, it is `recursive_bottom`, so that the recursion is canceled. + * 2. If the type is non-recursive and defines a `value_type`, then the `value_type` is used. + * If the `value_type` is a pair and a `mapped_type` is defined, + * then the `const` is removed from the first type. + * 3. If the type is recursive and `value_type` is not a pair, then `recursive_bottom` is returned. + * 4. If the type is recursive and `value_type` is a pair and a `mapped_type` is defined, + * then `const` is removed from the first type and the first type is returned. + * + * This behavior can be extended by the user as seen in test_stl_binders.cpp. + * + * This struct is exactly the same as impl_recursive_container_traits. + * The duplication achieves that user-defined specializations don't compete + * with internal specializations, but take precedence. + */ +template +struct recursive_container_traits : impl_recursive_container_traits {}; + +template +struct is_move_constructible + : all_of, + is_move_constructible< + typename recursive_container_traits::type_to_check_recursively>> {}; + +template <> +struct is_move_constructible : std::true_type {}; + +// Likewise for std::pair +// (after C++17 it is mandatory that the move constructor not exist when the two types aren't +// themselves move constructible, but this can not be relied upon when T1 or T2 are themselves +// containers). +template +struct is_move_constructible> + : all_of, is_move_constructible> {}; + // std::is_copy_constructible isn't quite enough: it lets std::vector (and similar) through when // T is non-copyable, but code containing such a copy constructor fails to actually compile. -template -struct is_copy_constructible : std::is_copy_constructible {}; +template +struct is_copy_constructible + : all_of, + is_copy_constructible< + typename recursive_container_traits::type_to_check_recursively>> {}; -// Specialization for types that appear to be copy constructible but also look like stl containers -// (we specifically check for: has `value_type` and `reference` with `reference = value_type&`): if -// so, copy constructability depends on whether the value_type is copy constructible. -template -struct is_copy_constructible< - Container, - enable_if_t< - all_of, - std::is_same, - // Avoid infinite recursion - negation>>::value>> - : is_copy_constructible {}; +template <> +struct is_copy_constructible : std::true_type {}; // Likewise for std::pair // (after C++17 it is mandatory that the copy constructor not exist when the two types aren't @@ -849,14 +1005,16 @@ struct is_copy_constructible> : all_of, is_copy_constructible> {}; // The same problems arise with std::is_copy_assignable, so we use the same workaround. -template -struct is_copy_assignable : std::is_copy_assignable {}; -template -struct is_copy_assignable, - std::is_same>::value>> - : is_copy_assignable {}; +template +struct is_copy_assignable + : all_of< + std::is_copy_assignable, + is_copy_assignable::type_to_check_recursively>> { +}; + +template <> +struct is_copy_assignable : std::true_type {}; + template struct is_copy_assignable> : all_of, is_copy_assignable> {}; @@ -994,7 +1152,7 @@ protected: return [](const void *arg) -> void * { return new T(*reinterpret_cast(arg)); }; } - template ::value>> + template ::value>> static auto make_move_constructor(const T *) -> decltype(new T(std::declval()), Constructor{}) { return [](const void *arg) -> void * { @@ -1006,5 +1164,14 @@ protected: static Constructor make_move_constructor(...) { return nullptr; } }; +PYBIND11_NOINLINE std::string type_info_description(const std::type_info &ti) { + if (auto *type_data = get_type_info(ti)) { + handle th((PyObject *) type_data->type); + return th.attr("__module__").cast() + '.' + + th.attr("__qualname__").cast(); + } + return clean_type_id(ti.name()); +} + PYBIND11_NAMESPACE_END(detail) PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE) diff --git a/pybind11/include/pybind11/eigen.h b/pybind11/include/pybind11/eigen.h index 4d221b3d7..273b9c930 100644 --- a/pybind11/include/pybind11/eigen.h +++ b/pybind11/include/pybind11/eigen.h @@ -9,700 +9,4 @@ #pragma once -/* HINT: To suppress warnings originating from the Eigen headers, use -isystem. - See also: - https://stackoverflow.com/questions/2579576/i-dir-vs-isystem-dir - https://stackoverflow.com/questions/1741816/isystem-for-ms-visual-studio-c-compiler -*/ - -#include "numpy.h" - -// The C4127 suppression was introduced for Eigen 3.4.0. In theory we could -// make it version specific, or even remove it later, but considering that -// 1. C4127 is generally far more distracting than useful for modern template code, and -// 2. we definitely want to ignore any MSVC warnings originating from Eigen code, -// it is probably best to keep this around indefinitely. -#if defined(_MSC_VER) -# pragma warning(push) -# pragma warning(disable : 4127) // C4127: conditional expression is constant -# pragma warning(disable : 5054) // https://github.com/pybind/pybind11/pull/3741 -// C5054: operator '&': deprecated between enumerations of different types -#endif - -#include -#include - -#if defined(_MSC_VER) -# pragma warning(pop) -#endif - -// Eigen prior to 3.2.7 doesn't have proper move constructors--but worse, some classes get implicit -// move constructors that break things. We could detect this an explicitly copy, but an extra copy -// of matrices seems highly undesirable. -static_assert(EIGEN_VERSION_AT_LEAST(3, 2, 7), - "Eigen support in pybind11 requires Eigen >= 3.2.7"); - -PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE) - -// Provide a convenience alias for easier pass-by-ref usage with fully dynamic strides: -using EigenDStride = Eigen::Stride; -template -using EigenDRef = Eigen::Ref; -template -using EigenDMap = Eigen::Map; - -PYBIND11_NAMESPACE_BEGIN(detail) - -#if EIGEN_VERSION_AT_LEAST(3, 3, 0) -using EigenIndex = Eigen::Index; -template -using EigenMapSparseMatrix = Eigen::Map>; -#else -using EigenIndex = EIGEN_DEFAULT_DENSE_INDEX_TYPE; -template -using EigenMapSparseMatrix = Eigen::MappedSparseMatrix; -#endif - -// Matches Eigen::Map, Eigen::Ref, blocks, etc: -template -using is_eigen_dense_map = all_of, - std::is_base_of, T>>; -template -using is_eigen_mutable_map = std::is_base_of, T>; -template -using is_eigen_dense_plain - = all_of>, is_template_base_of>; -template -using is_eigen_sparse = is_template_base_of; -// Test for objects inheriting from EigenBase that aren't captured by the above. This -// basically covers anything that can be assigned to a dense matrix but that don't have a typical -// matrix data layout that can be copied from their .data(). For example, DiagonalMatrix and -// SelfAdjointView fall into this category. -template -using is_eigen_other - = all_of, - negation, is_eigen_dense_plain, is_eigen_sparse>>>; - -// Captures numpy/eigen conformability status (returned by EigenProps::conformable()): -template -struct EigenConformable { - bool conformable = false; - EigenIndex rows = 0, cols = 0; - EigenDStride stride{0, 0}; // Only valid if negativestrides is false! - bool negativestrides = false; // If true, do not use stride! - - // NOLINTNEXTLINE(google-explicit-constructor) - EigenConformable(bool fits = false) : conformable{fits} {} - // Matrix type: - EigenConformable(EigenIndex r, EigenIndex c, EigenIndex rstride, EigenIndex cstride) - : conformable{true}, rows{r}, cols{c}, - // TODO: when Eigen bug #747 is fixed, remove the tests for non-negativity. - // http://eigen.tuxfamily.org/bz/show_bug.cgi?id=747 - stride{EigenRowMajor ? (rstride > 0 ? rstride : 0) - : (cstride > 0 ? cstride : 0) /* outer stride */, - EigenRowMajor ? (cstride > 0 ? cstride : 0) - : (rstride > 0 ? rstride : 0) /* inner stride */}, - negativestrides{rstride < 0 || cstride < 0} {} - // Vector type: - EigenConformable(EigenIndex r, EigenIndex c, EigenIndex stride) - : EigenConformable(r, c, r == 1 ? c * stride : stride, c == 1 ? r : r * stride) {} - - template - bool stride_compatible() const { - // To have compatible strides, we need (on both dimensions) one of fully dynamic strides, - // matching strides, or a dimension size of 1 (in which case the stride value is - // irrelevant). Alternatively, if any dimension size is 0, the strides are not relevant - // (and numpy ≥ 1.23 sets the strides to 0 in that case, so we need to check explicitly). - if (negativestrides) { - return false; - } - if (rows == 0 || cols == 0) { - return true; - } - return (props::inner_stride == Eigen::Dynamic || props::inner_stride == stride.inner() - || (EigenRowMajor ? cols : rows) == 1) - && (props::outer_stride == Eigen::Dynamic || props::outer_stride == stride.outer() - || (EigenRowMajor ? rows : cols) == 1); - } - // NOLINTNEXTLINE(google-explicit-constructor) - operator bool() const { return conformable; } -}; - -template -struct eigen_extract_stride { - using type = Type; -}; -template -struct eigen_extract_stride> { - using type = StrideType; -}; -template -struct eigen_extract_stride> { - using type = StrideType; -}; - -// Helper struct for extracting information from an Eigen type -template -struct EigenProps { - using Type = Type_; - using Scalar = typename Type::Scalar; - using StrideType = typename eigen_extract_stride::type; - static constexpr EigenIndex rows = Type::RowsAtCompileTime, cols = Type::ColsAtCompileTime, - size = Type::SizeAtCompileTime; - static constexpr bool row_major = Type::IsRowMajor, - vector - = Type::IsVectorAtCompileTime, // At least one dimension has fixed size 1 - fixed_rows = rows != Eigen::Dynamic, fixed_cols = cols != Eigen::Dynamic, - fixed = size != Eigen::Dynamic, // Fully-fixed size - dynamic = !fixed_rows && !fixed_cols; // Fully-dynamic size - - template - using if_zero = std::integral_constant; - static constexpr EigenIndex inner_stride - = if_zero::value, - outer_stride = if_zero < StrideType::OuterStrideAtCompileTime, - vector ? size - : row_major ? cols - : rows > ::value; - static constexpr bool dynamic_stride - = inner_stride == Eigen::Dynamic && outer_stride == Eigen::Dynamic; - static constexpr bool requires_row_major - = !dynamic_stride && !vector && (row_major ? inner_stride : outer_stride) == 1; - static constexpr bool requires_col_major - = !dynamic_stride && !vector && (row_major ? outer_stride : inner_stride) == 1; - - // Takes an input array and determines whether we can make it fit into the Eigen type. If - // the array is a vector, we attempt to fit it into either an Eigen 1xN or Nx1 vector - // (preferring the latter if it will fit in either, i.e. for a fully dynamic matrix type). - static EigenConformable conformable(const array &a) { - const auto dims = a.ndim(); - if (dims < 1 || dims > 2) { - return false; - } - - if (dims == 2) { // Matrix type: require exact match (or dynamic) - - EigenIndex np_rows = a.shape(0), np_cols = a.shape(1), - np_rstride = a.strides(0) / static_cast(sizeof(Scalar)), - np_cstride = a.strides(1) / static_cast(sizeof(Scalar)); - if ((PYBIND11_SILENCE_MSVC_C4127(fixed_rows) && np_rows != rows) - || (PYBIND11_SILENCE_MSVC_C4127(fixed_cols) && np_cols != cols)) { - return false; - } - - return {np_rows, np_cols, np_rstride, np_cstride}; - } - - // Otherwise we're storing an n-vector. Only one of the strides will be used, but - // whichever is used, we want the (single) numpy stride value. - const EigenIndex n = a.shape(0), - stride = a.strides(0) / static_cast(sizeof(Scalar)); - - if (vector) { // Eigen type is a compile-time vector - if (PYBIND11_SILENCE_MSVC_C4127(fixed) && size != n) { - return false; // Vector size mismatch - } - return {rows == 1 ? 1 : n, cols == 1 ? 1 : n, stride}; - } - if (fixed) { - // The type has a fixed size, but is not a vector: abort - return false; - } - if (fixed_cols) { - // Since this isn't a vector, cols must be != 1. We allow this only if it exactly - // equals the number of elements (rows is Dynamic, and so 1 row is allowed). - if (cols != n) { - return false; - } - return {1, n, stride}; - } // Otherwise it's either fully dynamic, or column dynamic; both become a column vector - if (PYBIND11_SILENCE_MSVC_C4127(fixed_rows) && rows != n) { - return false; - } - return {n, 1, stride}; - } - - static constexpr bool show_writeable - = is_eigen_dense_map::value && is_eigen_mutable_map::value; - static constexpr bool show_order = is_eigen_dense_map::value; - static constexpr bool show_c_contiguous = show_order && requires_row_major; - static constexpr bool show_f_contiguous - = !show_c_contiguous && show_order && requires_col_major; - - static constexpr auto descriptor - = const_name("numpy.ndarray[") + npy_format_descriptor::name + const_name("[") - + const_name(const_name<(size_t) rows>(), const_name("m")) + const_name(", ") - + const_name(const_name<(size_t) cols>(), const_name("n")) + const_name("]") - + - // For a reference type (e.g. Ref) we have other constraints that might need to - // be satisfied: writeable=True (for a mutable reference), and, depending on the map's - // stride options, possibly f_contiguous or c_contiguous. We include them in the - // descriptor output to provide some hint as to why a TypeError is occurring (otherwise - // it can be confusing to see that a function accepts a 'numpy.ndarray[float64[3,2]]' and - // an error message that you *gave* a numpy.ndarray of the right type and dimensions. - const_name(", flags.writeable", "") - + const_name(", flags.c_contiguous", "") - + const_name(", flags.f_contiguous", "") + const_name("]"); -}; - -// Casts an Eigen type to numpy array. If given a base, the numpy array references the src data, -// otherwise it'll make a copy. writeable lets you turn off the writeable flag for the array. -template -handle -eigen_array_cast(typename props::Type const &src, handle base = handle(), bool writeable = true) { - constexpr ssize_t elem_size = sizeof(typename props::Scalar); - array a; - if (props::vector) { - a = array({src.size()}, {elem_size * src.innerStride()}, src.data(), base); - } else { - a = array({src.rows(), src.cols()}, - {elem_size * src.rowStride(), elem_size * src.colStride()}, - src.data(), - base); - } - - if (!writeable) { - array_proxy(a.ptr())->flags &= ~detail::npy_api::NPY_ARRAY_WRITEABLE_; - } - - return a.release(); -} - -// Takes an lvalue ref to some Eigen type and a (python) base object, creating a numpy array that -// reference the Eigen object's data with `base` as the python-registered base class (if omitted, -// the base will be set to None, and lifetime management is up to the caller). The numpy array is -// non-writeable if the given type is const. -template -handle eigen_ref_array(Type &src, handle parent = none()) { - // none here is to get past array's should-we-copy detection, which currently always - // copies when there is no base. Setting the base to None should be harmless. - return eigen_array_cast(src, parent, !std::is_const::value); -} - -// Takes a pointer to some dense, plain Eigen type, builds a capsule around it, then returns a -// numpy array that references the encapsulated data with a python-side reference to the capsule to -// tie its destruction to that of any dependent python objects. Const-ness is determined by -// whether or not the Type of the pointer given is const. -template ::value>> -handle eigen_encapsulate(Type *src) { - capsule base(src, [](void *o) { delete static_cast(o); }); - return eigen_ref_array(*src, base); -} - -// Type caster for regular, dense matrix types (e.g. MatrixXd), but not maps/refs/etc. of dense -// types. -template -struct type_caster::value>> { - using Scalar = typename Type::Scalar; - using props = EigenProps; - - bool load(handle src, bool convert) { - // If we're in no-convert mode, only load if given an array of the correct type - if (!convert && !isinstance>(src)) { - return false; - } - - // Coerce into an array, but don't do type conversion yet; the copy below handles it. - auto buf = array::ensure(src); - - if (!buf) { - return false; - } - - auto dims = buf.ndim(); - if (dims < 1 || dims > 2) { - return false; - } - - auto fits = props::conformable(buf); - if (!fits) { - return false; - } - - // Allocate the new type, then build a numpy reference into it - value = Type(fits.rows, fits.cols); - auto ref = reinterpret_steal(eigen_ref_array(value)); - if (dims == 1) { - ref = ref.squeeze(); - } else if (ref.ndim() == 1) { - buf = buf.squeeze(); - } - - int result = detail::npy_api::get().PyArray_CopyInto_(ref.ptr(), buf.ptr()); - - if (result < 0) { // Copy failed! - PyErr_Clear(); - return false; - } - - return true; - } - -private: - // Cast implementation - template - static handle cast_impl(CType *src, return_value_policy policy, handle parent) { - switch (policy) { - case return_value_policy::take_ownership: - case return_value_policy::automatic: - return eigen_encapsulate(src); - case return_value_policy::move: - return eigen_encapsulate(new CType(std::move(*src))); - case return_value_policy::copy: - return eigen_array_cast(*src); - case return_value_policy::reference: - case return_value_policy::automatic_reference: - return eigen_ref_array(*src); - case return_value_policy::reference_internal: - return eigen_ref_array(*src, parent); - default: - throw cast_error("unhandled return_value_policy: should not happen!"); - }; - } - -public: - // Normal returned non-reference, non-const value: - static handle cast(Type &&src, return_value_policy /* policy */, handle parent) { - return cast_impl(&src, return_value_policy::move, parent); - } - // If you return a non-reference const, we mark the numpy array readonly: - static handle cast(const Type &&src, return_value_policy /* policy */, handle parent) { - return cast_impl(&src, return_value_policy::move, parent); - } - // lvalue reference return; default (automatic) becomes copy - static handle cast(Type &src, return_value_policy policy, handle parent) { - if (policy == return_value_policy::automatic - || policy == return_value_policy::automatic_reference) { - policy = return_value_policy::copy; - } - return cast_impl(&src, policy, parent); - } - // const lvalue reference return; default (automatic) becomes copy - static handle cast(const Type &src, return_value_policy policy, handle parent) { - if (policy == return_value_policy::automatic - || policy == return_value_policy::automatic_reference) { - policy = return_value_policy::copy; - } - return cast(&src, policy, parent); - } - // non-const pointer return - static handle cast(Type *src, return_value_policy policy, handle parent) { - return cast_impl(src, policy, parent); - } - // const pointer return - static handle cast(const Type *src, return_value_policy policy, handle parent) { - return cast_impl(src, policy, parent); - } - - static constexpr auto name = props::descriptor; - - // NOLINTNEXTLINE(google-explicit-constructor) - operator Type *() { return &value; } - // NOLINTNEXTLINE(google-explicit-constructor) - operator Type &() { return value; } - // NOLINTNEXTLINE(google-explicit-constructor) - operator Type &&() && { return std::move(value); } - template - using cast_op_type = movable_cast_op_type; - -private: - Type value; -}; - -// Base class for casting reference/map/block/etc. objects back to python. -template -struct eigen_map_caster { -private: - using props = EigenProps; - -public: - // Directly referencing a ref/map's data is a bit dangerous (whatever the map/ref points to has - // to stay around), but we'll allow it under the assumption that you know what you're doing - // (and have an appropriate keep_alive in place). We return a numpy array pointing directly at - // the ref's data (The numpy array ends up read-only if the ref was to a const matrix type.) - // Note that this means you need to ensure you don't destroy the object in some other way (e.g. - // with an appropriate keep_alive, or with a reference to a statically allocated matrix). - static handle cast(const MapType &src, return_value_policy policy, handle parent) { - switch (policy) { - case return_value_policy::copy: - return eigen_array_cast(src); - case return_value_policy::reference_internal: - return eigen_array_cast(src, parent, is_eigen_mutable_map::value); - case return_value_policy::reference: - case return_value_policy::automatic: - case return_value_policy::automatic_reference: - return eigen_array_cast(src, none(), is_eigen_mutable_map::value); - default: - // move, take_ownership don't make any sense for a ref/map: - pybind11_fail("Invalid return_value_policy for Eigen Map/Ref/Block type"); - } - } - - static constexpr auto name = props::descriptor; - - // Explicitly delete these: support python -> C++ conversion on these (i.e. these can be return - // types but not bound arguments). We still provide them (with an explicitly delete) so that - // you end up here if you try anyway. - bool load(handle, bool) = delete; - operator MapType() = delete; - template - using cast_op_type = MapType; -}; - -// We can return any map-like object (but can only load Refs, specialized next): -template -struct type_caster::value>> : eigen_map_caster {}; - -// Loader for Ref<...> arguments. See the documentation for info on how to make this work without -// copying (it requires some extra effort in many cases). -template -struct type_caster< - Eigen::Ref, - enable_if_t>::value>> - : public eigen_map_caster> { -private: - using Type = Eigen::Ref; - using props = EigenProps; - using Scalar = typename props::Scalar; - using MapType = Eigen::Map; - using Array - = array_t; - static constexpr bool need_writeable = is_eigen_mutable_map::value; - // Delay construction (these have no default constructor) - std::unique_ptr map; - std::unique_ptr ref; - // Our array. When possible, this is just a numpy array pointing to the source data, but - // sometimes we can't avoid copying (e.g. input is not a numpy array at all, has an - // incompatible layout, or is an array of a type that needs to be converted). Using a numpy - // temporary (rather than an Eigen temporary) saves an extra copy when we need both type - // conversion and storage order conversion. (Note that we refuse to use this temporary copy - // when loading an argument for a Ref with M non-const, i.e. a read-write reference). - Array copy_or_ref; - -public: - bool load(handle src, bool convert) { - // First check whether what we have is already an array of the right type. If not, we - // can't avoid a copy (because the copy is also going to do type conversion). - bool need_copy = !isinstance(src); - - EigenConformable fits; - if (!need_copy) { - // We don't need a converting copy, but we also need to check whether the strides are - // compatible with the Ref's stride requirements - auto aref = reinterpret_borrow(src); - - if (aref && (!need_writeable || aref.writeable())) { - fits = props::conformable(aref); - if (!fits) { - return false; // Incompatible dimensions - } - if (!fits.template stride_compatible()) { - need_copy = true; - } else { - copy_or_ref = std::move(aref); - } - } else { - need_copy = true; - } - } - - if (need_copy) { - // We need to copy: If we need a mutable reference, or we're not supposed to convert - // (either because we're in the no-convert overload pass, or because we're explicitly - // instructed not to copy (via `py::arg().noconvert()`) we have to fail loading. - if (!convert || need_writeable) { - return false; - } - - Array copy = Array::ensure(src); - if (!copy) { - return false; - } - fits = props::conformable(copy); - if (!fits || !fits.template stride_compatible()) { - return false; - } - copy_or_ref = std::move(copy); - loader_life_support::add_patient(copy_or_ref); - } - - ref.reset(); - map.reset(new MapType(data(copy_or_ref), - fits.rows, - fits.cols, - make_stride(fits.stride.outer(), fits.stride.inner()))); - ref.reset(new Type(*map)); - - return true; - } - - // NOLINTNEXTLINE(google-explicit-constructor) - operator Type *() { return ref.get(); } - // NOLINTNEXTLINE(google-explicit-constructor) - operator Type &() { return *ref; } - template - using cast_op_type = pybind11::detail::cast_op_type<_T>; - -private: - template ::value, int> = 0> - Scalar *data(Array &a) { - return a.mutable_data(); - } - - template ::value, int> = 0> - const Scalar *data(Array &a) { - return a.data(); - } - - // Attempt to figure out a constructor of `Stride` that will work. - // If both strides are fixed, use a default constructor: - template - using stride_ctor_default = bool_constant::value>; - // Otherwise, if there is a two-index constructor, assume it is (outer,inner) like - // Eigen::Stride, and use it: - template - using stride_ctor_dual - = bool_constant::value - && std::is_constructible::value>; - // Otherwise, if there is a one-index constructor, and just one of the strides is dynamic, use - // it (passing whichever stride is dynamic). - template - using stride_ctor_outer - = bool_constant, stride_ctor_dual>::value - && S::OuterStrideAtCompileTime == Eigen::Dynamic - && S::InnerStrideAtCompileTime != Eigen::Dynamic - && std::is_constructible::value>; - template - using stride_ctor_inner - = bool_constant, stride_ctor_dual>::value - && S::InnerStrideAtCompileTime == Eigen::Dynamic - && S::OuterStrideAtCompileTime != Eigen::Dynamic - && std::is_constructible::value>; - - template ::value, int> = 0> - static S make_stride(EigenIndex, EigenIndex) { - return S(); - } - template ::value, int> = 0> - static S make_stride(EigenIndex outer, EigenIndex inner) { - return S(outer, inner); - } - template ::value, int> = 0> - static S make_stride(EigenIndex outer, EigenIndex) { - return S(outer); - } - template ::value, int> = 0> - static S make_stride(EigenIndex, EigenIndex inner) { - return S(inner); - } -}; - -// type_caster for special matrix types (e.g. DiagonalMatrix), which are EigenBase, but not -// EigenDense (i.e. they don't have a data(), at least not with the usual matrix layout). -// load() is not supported, but we can cast them into the python domain by first copying to a -// regular Eigen::Matrix, then casting that. -template -struct type_caster::value>> { -protected: - using Matrix - = Eigen::Matrix; - using props = EigenProps; - -public: - static handle cast(const Type &src, return_value_policy /* policy */, handle /* parent */) { - handle h = eigen_encapsulate(new Matrix(src)); - return h; - } - static handle cast(const Type *src, return_value_policy policy, handle parent) { - return cast(*src, policy, parent); - } - - static constexpr auto name = props::descriptor; - - // Explicitly delete these: support python -> C++ conversion on these (i.e. these can be return - // types but not bound arguments). We still provide them (with an explicitly delete) so that - // you end up here if you try anyway. - bool load(handle, bool) = delete; - operator Type() = delete; - template - using cast_op_type = Type; -}; - -template -struct type_caster::value>> { - using Scalar = typename Type::Scalar; - using StorageIndex = remove_reference_t().outerIndexPtr())>; - using Index = typename Type::Index; - static constexpr bool rowMajor = Type::IsRowMajor; - - bool load(handle src, bool) { - if (!src) { - return false; - } - - auto obj = reinterpret_borrow(src); - object sparse_module = module_::import("scipy.sparse"); - object matrix_type = sparse_module.attr(rowMajor ? "csr_matrix" : "csc_matrix"); - - if (!type::handle_of(obj).is(matrix_type)) { - try { - obj = matrix_type(obj); - } catch (const error_already_set &) { - return false; - } - } - - auto values = array_t((object) obj.attr("data")); - auto innerIndices = array_t((object) obj.attr("indices")); - auto outerIndices = array_t((object) obj.attr("indptr")); - auto shape = pybind11::tuple((pybind11::object) obj.attr("shape")); - auto nnz = obj.attr("nnz").cast(); - - if (!values || !innerIndices || !outerIndices) { - return false; - } - - value = EigenMapSparseMatrix(shape[0].cast(), - shape[1].cast(), - std::move(nnz), - outerIndices.mutable_data(), - innerIndices.mutable_data(), - values.mutable_data()); - - return true; - } - - static handle cast(const Type &src, return_value_policy /* policy */, handle /* parent */) { - const_cast(src).makeCompressed(); - - object matrix_type - = module_::import("scipy.sparse").attr(rowMajor ? "csr_matrix" : "csc_matrix"); - - array data(src.nonZeros(), src.valuePtr()); - array outerIndices((rowMajor ? src.rows() : src.cols()) + 1, src.outerIndexPtr()); - array innerIndices(src.nonZeros(), src.innerIndexPtr()); - - return matrix_type(pybind11::make_tuple( - std::move(data), std::move(innerIndices), std::move(outerIndices)), - pybind11::make_tuple(src.rows(), src.cols())) - .release(); - } - - PYBIND11_TYPE_CASTER(Type, - const_name<(Type::IsRowMajor) != 0>("scipy.sparse.csr_matrix[", - "scipy.sparse.csc_matrix[") - + npy_format_descriptor::name + const_name("]")); -}; - -PYBIND11_NAMESPACE_END(detail) -PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE) +#include "eigen/matrix.h" diff --git a/pybind11/include/pybind11/eigen/common.h b/pybind11/include/pybind11/eigen/common.h new file mode 100644 index 000000000..24f56d158 --- /dev/null +++ b/pybind11/include/pybind11/eigen/common.h @@ -0,0 +1,9 @@ +// Copyright (c) 2023 The pybind Community. + +#pragma once + +// Common message for `static_assert()`s, which are useful to easily +// preempt much less obvious errors. +#define PYBIND11_EIGEN_MESSAGE_POINTER_TYPES_ARE_NOT_SUPPORTED \ + "Pointer types (in particular `PyObject *`) are not supported as scalar types for Eigen " \ + "types." diff --git a/pybind11/include/pybind11/eigen/matrix.h b/pybind11/include/pybind11/eigen/matrix.h new file mode 100644 index 000000000..8d4342f81 --- /dev/null +++ b/pybind11/include/pybind11/eigen/matrix.h @@ -0,0 +1,714 @@ +/* + pybind11/eigen/matrix.h: Transparent conversion for dense and sparse Eigen matrices + + Copyright (c) 2016 Wenzel Jakob + + All rights reserved. Use of this source code is governed by a + BSD-style license that can be found in the LICENSE file. +*/ + +#pragma once + +#include "../numpy.h" +#include "common.h" + +/* HINT: To suppress warnings originating from the Eigen headers, use -isystem. + See also: + https://stackoverflow.com/questions/2579576/i-dir-vs-isystem-dir + https://stackoverflow.com/questions/1741816/isystem-for-ms-visual-studio-c-compiler +*/ +PYBIND11_WARNING_PUSH +PYBIND11_WARNING_DISABLE_MSVC(5054) // https://github.com/pybind/pybind11/pull/3741 +// C5054: operator '&': deprecated between enumerations of different types +#if defined(__MINGW32__) +PYBIND11_WARNING_DISABLE_GCC("-Wmaybe-uninitialized") +#endif + +#include +#include + +PYBIND11_WARNING_POP + +// Eigen prior to 3.2.7 doesn't have proper move constructors--but worse, some classes get implicit +// move constructors that break things. We could detect this an explicitly copy, but an extra copy +// of matrices seems highly undesirable. +static_assert(EIGEN_VERSION_AT_LEAST(3, 2, 7), + "Eigen matrix support in pybind11 requires Eigen >= 3.2.7"); + +PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE) + +PYBIND11_WARNING_DISABLE_MSVC(4127) + +// Provide a convenience alias for easier pass-by-ref usage with fully dynamic strides: +using EigenDStride = Eigen::Stride; +template +using EigenDRef = Eigen::Ref; +template +using EigenDMap = Eigen::Map; + +PYBIND11_NAMESPACE_BEGIN(detail) + +#if EIGEN_VERSION_AT_LEAST(3, 3, 0) +using EigenIndex = Eigen::Index; +template +using EigenMapSparseMatrix = Eigen::Map>; +#else +using EigenIndex = EIGEN_DEFAULT_DENSE_INDEX_TYPE; +template +using EigenMapSparseMatrix = Eigen::MappedSparseMatrix; +#endif + +// Matches Eigen::Map, Eigen::Ref, blocks, etc: +template +using is_eigen_dense_map = all_of, + std::is_base_of, T>>; +template +using is_eigen_mutable_map = std::is_base_of, T>; +template +using is_eigen_dense_plain + = all_of>, is_template_base_of>; +template +using is_eigen_sparse = is_template_base_of; +// Test for objects inheriting from EigenBase that aren't captured by the above. This +// basically covers anything that can be assigned to a dense matrix but that don't have a typical +// matrix data layout that can be copied from their .data(). For example, DiagonalMatrix and +// SelfAdjointView fall into this category. +template +using is_eigen_other + = all_of, + negation, is_eigen_dense_plain, is_eigen_sparse>>>; + +// Captures numpy/eigen conformability status (returned by EigenProps::conformable()): +template +struct EigenConformable { + bool conformable = false; + EigenIndex rows = 0, cols = 0; + EigenDStride stride{0, 0}; // Only valid if negativestrides is false! + bool negativestrides = false; // If true, do not use stride! + + // NOLINTNEXTLINE(google-explicit-constructor) + EigenConformable(bool fits = false) : conformable{fits} {} + // Matrix type: + EigenConformable(EigenIndex r, EigenIndex c, EigenIndex rstride, EigenIndex cstride) + : conformable{true}, rows{r}, cols{c}, + // TODO: when Eigen bug #747 is fixed, remove the tests for non-negativity. + // http://eigen.tuxfamily.org/bz/show_bug.cgi?id=747 + stride{EigenRowMajor ? (rstride > 0 ? rstride : 0) + : (cstride > 0 ? cstride : 0) /* outer stride */, + EigenRowMajor ? (cstride > 0 ? cstride : 0) + : (rstride > 0 ? rstride : 0) /* inner stride */}, + negativestrides{rstride < 0 || cstride < 0} {} + // Vector type: + EigenConformable(EigenIndex r, EigenIndex c, EigenIndex stride) + : EigenConformable(r, c, r == 1 ? c * stride : stride, c == 1 ? r : r * stride) {} + + template + bool stride_compatible() const { + // To have compatible strides, we need (on both dimensions) one of fully dynamic strides, + // matching strides, or a dimension size of 1 (in which case the stride value is + // irrelevant). Alternatively, if any dimension size is 0, the strides are not relevant + // (and numpy ≥ 1.23 sets the strides to 0 in that case, so we need to check explicitly). + if (negativestrides) { + return false; + } + if (rows == 0 || cols == 0) { + return true; + } + return (props::inner_stride == Eigen::Dynamic || props::inner_stride == stride.inner() + || (EigenRowMajor ? cols : rows) == 1) + && (props::outer_stride == Eigen::Dynamic || props::outer_stride == stride.outer() + || (EigenRowMajor ? rows : cols) == 1); + } + // NOLINTNEXTLINE(google-explicit-constructor) + operator bool() const { return conformable; } +}; + +template +struct eigen_extract_stride { + using type = Type; +}; +template +struct eigen_extract_stride> { + using type = StrideType; +}; +template +struct eigen_extract_stride> { + using type = StrideType; +}; + +// Helper struct for extracting information from an Eigen type +template +struct EigenProps { + using Type = Type_; + using Scalar = typename Type::Scalar; + using StrideType = typename eigen_extract_stride::type; + static constexpr EigenIndex rows = Type::RowsAtCompileTime, cols = Type::ColsAtCompileTime, + size = Type::SizeAtCompileTime; + static constexpr bool row_major = Type::IsRowMajor, + vector + = Type::IsVectorAtCompileTime, // At least one dimension has fixed size 1 + fixed_rows = rows != Eigen::Dynamic, fixed_cols = cols != Eigen::Dynamic, + fixed = size != Eigen::Dynamic, // Fully-fixed size + dynamic = !fixed_rows && !fixed_cols; // Fully-dynamic size + + template + using if_zero = std::integral_constant; + static constexpr EigenIndex inner_stride + = if_zero::value, + outer_stride = if_zero < StrideType::OuterStrideAtCompileTime, + vector ? size + : row_major ? cols + : rows > ::value; + static constexpr bool dynamic_stride + = inner_stride == Eigen::Dynamic && outer_stride == Eigen::Dynamic; + static constexpr bool requires_row_major + = !dynamic_stride && !vector && (row_major ? inner_stride : outer_stride) == 1; + static constexpr bool requires_col_major + = !dynamic_stride && !vector && (row_major ? outer_stride : inner_stride) == 1; + + // Takes an input array and determines whether we can make it fit into the Eigen type. If + // the array is a vector, we attempt to fit it into either an Eigen 1xN or Nx1 vector + // (preferring the latter if it will fit in either, i.e. for a fully dynamic matrix type). + static EigenConformable conformable(const array &a) { + const auto dims = a.ndim(); + if (dims < 1 || dims > 2) { + return false; + } + + if (dims == 2) { // Matrix type: require exact match (or dynamic) + + EigenIndex np_rows = a.shape(0), np_cols = a.shape(1), + np_rstride = a.strides(0) / static_cast(sizeof(Scalar)), + np_cstride = a.strides(1) / static_cast(sizeof(Scalar)); + if ((fixed_rows && np_rows != rows) || (fixed_cols && np_cols != cols)) { + return false; + } + + return {np_rows, np_cols, np_rstride, np_cstride}; + } + + // Otherwise we're storing an n-vector. Only one of the strides will be used, but + // whichever is used, we want the (single) numpy stride value. + const EigenIndex n = a.shape(0), + stride = a.strides(0) / static_cast(sizeof(Scalar)); + + if (vector) { // Eigen type is a compile-time vector + if (fixed && size != n) { + return false; // Vector size mismatch + } + return {rows == 1 ? 1 : n, cols == 1 ? 1 : n, stride}; + } + if (fixed) { + // The type has a fixed size, but is not a vector: abort + return false; + } + if (fixed_cols) { + // Since this isn't a vector, cols must be != 1. We allow this only if it exactly + // equals the number of elements (rows is Dynamic, and so 1 row is allowed). + if (cols != n) { + return false; + } + return {1, n, stride}; + } // Otherwise it's either fully dynamic, or column dynamic; both become a column vector + if (fixed_rows && rows != n) { + return false; + } + return {n, 1, stride}; + } + + static constexpr bool show_writeable + = is_eigen_dense_map::value && is_eigen_mutable_map::value; + static constexpr bool show_order = is_eigen_dense_map::value; + static constexpr bool show_c_contiguous = show_order && requires_row_major; + static constexpr bool show_f_contiguous + = !show_c_contiguous && show_order && requires_col_major; + + static constexpr auto descriptor + = const_name("numpy.ndarray[") + npy_format_descriptor::name + const_name("[") + + const_name(const_name<(size_t) rows>(), const_name("m")) + const_name(", ") + + const_name(const_name<(size_t) cols>(), const_name("n")) + const_name("]") + + + // For a reference type (e.g. Ref) we have other constraints that might need to + // be satisfied: writeable=True (for a mutable reference), and, depending on the map's + // stride options, possibly f_contiguous or c_contiguous. We include them in the + // descriptor output to provide some hint as to why a TypeError is occurring (otherwise + // it can be confusing to see that a function accepts a 'numpy.ndarray[float64[3,2]]' and + // an error message that you *gave* a numpy.ndarray of the right type and dimensions. + const_name(", flags.writeable", "") + + const_name(", flags.c_contiguous", "") + + const_name(", flags.f_contiguous", "") + const_name("]"); +}; + +// Casts an Eigen type to numpy array. If given a base, the numpy array references the src data, +// otherwise it'll make a copy. writeable lets you turn off the writeable flag for the array. +template +handle +eigen_array_cast(typename props::Type const &src, handle base = handle(), bool writeable = true) { + constexpr ssize_t elem_size = sizeof(typename props::Scalar); + array a; + if (props::vector) { + a = array({src.size()}, {elem_size * src.innerStride()}, src.data(), base); + } else { + a = array({src.rows(), src.cols()}, + {elem_size * src.rowStride(), elem_size * src.colStride()}, + src.data(), + base); + } + + if (!writeable) { + array_proxy(a.ptr())->flags &= ~detail::npy_api::NPY_ARRAY_WRITEABLE_; + } + + return a.release(); +} + +// Takes an lvalue ref to some Eigen type and a (python) base object, creating a numpy array that +// reference the Eigen object's data with `base` as the python-registered base class (if omitted, +// the base will be set to None, and lifetime management is up to the caller). The numpy array is +// non-writeable if the given type is const. +template +handle eigen_ref_array(Type &src, handle parent = none()) { + // none here is to get past array's should-we-copy detection, which currently always + // copies when there is no base. Setting the base to None should be harmless. + return eigen_array_cast(src, parent, !std::is_const::value); +} + +// Takes a pointer to some dense, plain Eigen type, builds a capsule around it, then returns a +// numpy array that references the encapsulated data with a python-side reference to the capsule to +// tie its destruction to that of any dependent python objects. Const-ness is determined by +// whether or not the Type of the pointer given is const. +template ::value>> +handle eigen_encapsulate(Type *src) { + capsule base(src, [](void *o) { delete static_cast(o); }); + return eigen_ref_array(*src, base); +} + +// Type caster for regular, dense matrix types (e.g. MatrixXd), but not maps/refs/etc. of dense +// types. +template +struct type_caster::value>> { + using Scalar = typename Type::Scalar; + static_assert(!std::is_pointer::value, + PYBIND11_EIGEN_MESSAGE_POINTER_TYPES_ARE_NOT_SUPPORTED); + using props = EigenProps; + + bool load(handle src, bool convert) { + // If we're in no-convert mode, only load if given an array of the correct type + if (!convert && !isinstance>(src)) { + return false; + } + + // Coerce into an array, but don't do type conversion yet; the copy below handles it. + auto buf = array::ensure(src); + + if (!buf) { + return false; + } + + auto dims = buf.ndim(); + if (dims < 1 || dims > 2) { + return false; + } + + auto fits = props::conformable(buf); + if (!fits) { + return false; + } + + // Allocate the new type, then build a numpy reference into it + value = Type(fits.rows, fits.cols); + auto ref = reinterpret_steal(eigen_ref_array(value)); + if (dims == 1) { + ref = ref.squeeze(); + } else if (ref.ndim() == 1) { + buf = buf.squeeze(); + } + + int result = detail::npy_api::get().PyArray_CopyInto_(ref.ptr(), buf.ptr()); + + if (result < 0) { // Copy failed! + PyErr_Clear(); + return false; + } + + return true; + } + +private: + // Cast implementation + template + static handle cast_impl(CType *src, return_value_policy policy, handle parent) { + switch (policy) { + case return_value_policy::take_ownership: + case return_value_policy::automatic: + return eigen_encapsulate(src); + case return_value_policy::move: + return eigen_encapsulate(new CType(std::move(*src))); + case return_value_policy::copy: + return eigen_array_cast(*src); + case return_value_policy::reference: + case return_value_policy::automatic_reference: + return eigen_ref_array(*src); + case return_value_policy::reference_internal: + return eigen_ref_array(*src, parent); + default: + throw cast_error("unhandled return_value_policy: should not happen!"); + }; + } + +public: + // Normal returned non-reference, non-const value: + static handle cast(Type &&src, return_value_policy /* policy */, handle parent) { + return cast_impl(&src, return_value_policy::move, parent); + } + // If you return a non-reference const, we mark the numpy array readonly: + static handle cast(const Type &&src, return_value_policy /* policy */, handle parent) { + return cast_impl(&src, return_value_policy::move, parent); + } + // lvalue reference return; default (automatic) becomes copy + static handle cast(Type &src, return_value_policy policy, handle parent) { + if (policy == return_value_policy::automatic + || policy == return_value_policy::automatic_reference) { + policy = return_value_policy::copy; + } + return cast_impl(&src, policy, parent); + } + // const lvalue reference return; default (automatic) becomes copy + static handle cast(const Type &src, return_value_policy policy, handle parent) { + if (policy == return_value_policy::automatic + || policy == return_value_policy::automatic_reference) { + policy = return_value_policy::copy; + } + return cast(&src, policy, parent); + } + // non-const pointer return + static handle cast(Type *src, return_value_policy policy, handle parent) { + return cast_impl(src, policy, parent); + } + // const pointer return + static handle cast(const Type *src, return_value_policy policy, handle parent) { + return cast_impl(src, policy, parent); + } + + static constexpr auto name = props::descriptor; + + // NOLINTNEXTLINE(google-explicit-constructor) + operator Type *() { return &value; } + // NOLINTNEXTLINE(google-explicit-constructor) + operator Type &() { return value; } + // NOLINTNEXTLINE(google-explicit-constructor) + operator Type &&() && { return std::move(value); } + template + using cast_op_type = movable_cast_op_type; + +private: + Type value; +}; + +// Base class for casting reference/map/block/etc. objects back to python. +template +struct eigen_map_caster { + static_assert(!std::is_pointer::value, + PYBIND11_EIGEN_MESSAGE_POINTER_TYPES_ARE_NOT_SUPPORTED); + +private: + using props = EigenProps; + +public: + // Directly referencing a ref/map's data is a bit dangerous (whatever the map/ref points to has + // to stay around), but we'll allow it under the assumption that you know what you're doing + // (and have an appropriate keep_alive in place). We return a numpy array pointing directly at + // the ref's data (The numpy array ends up read-only if the ref was to a const matrix type.) + // Note that this means you need to ensure you don't destroy the object in some other way (e.g. + // with an appropriate keep_alive, or with a reference to a statically allocated matrix). + static handle cast(const MapType &src, return_value_policy policy, handle parent) { + switch (policy) { + case return_value_policy::copy: + return eigen_array_cast(src); + case return_value_policy::reference_internal: + return eigen_array_cast(src, parent, is_eigen_mutable_map::value); + case return_value_policy::reference: + case return_value_policy::automatic: + case return_value_policy::automatic_reference: + return eigen_array_cast(src, none(), is_eigen_mutable_map::value); + default: + // move, take_ownership don't make any sense for a ref/map: + pybind11_fail("Invalid return_value_policy for Eigen Map/Ref/Block type"); + } + } + + static constexpr auto name = props::descriptor; + + // Explicitly delete these: support python -> C++ conversion on these (i.e. these can be return + // types but not bound arguments). We still provide them (with an explicitly delete) so that + // you end up here if you try anyway. + bool load(handle, bool) = delete; + operator MapType() = delete; + template + using cast_op_type = MapType; +}; + +// We can return any map-like object (but can only load Refs, specialized next): +template +struct type_caster::value>> : eigen_map_caster {}; + +// Loader for Ref<...> arguments. See the documentation for info on how to make this work without +// copying (it requires some extra effort in many cases). +template +struct type_caster< + Eigen::Ref, + enable_if_t>::value>> + : public eigen_map_caster> { +private: + using Type = Eigen::Ref; + using props = EigenProps; + using Scalar = typename props::Scalar; + static_assert(!std::is_pointer::value, + PYBIND11_EIGEN_MESSAGE_POINTER_TYPES_ARE_NOT_SUPPORTED); + using MapType = Eigen::Map; + using Array + = array_t; + static constexpr bool need_writeable = is_eigen_mutable_map::value; + // Delay construction (these have no default constructor) + std::unique_ptr map; + std::unique_ptr ref; + // Our array. When possible, this is just a numpy array pointing to the source data, but + // sometimes we can't avoid copying (e.g. input is not a numpy array at all, has an + // incompatible layout, or is an array of a type that needs to be converted). Using a numpy + // temporary (rather than an Eigen temporary) saves an extra copy when we need both type + // conversion and storage order conversion. (Note that we refuse to use this temporary copy + // when loading an argument for a Ref with M non-const, i.e. a read-write reference). + Array copy_or_ref; + +public: + bool load(handle src, bool convert) { + // First check whether what we have is already an array of the right type. If not, we + // can't avoid a copy (because the copy is also going to do type conversion). + bool need_copy = !isinstance(src); + + EigenConformable fits; + if (!need_copy) { + // We don't need a converting copy, but we also need to check whether the strides are + // compatible with the Ref's stride requirements + auto aref = reinterpret_borrow(src); + + if (aref && (!need_writeable || aref.writeable())) { + fits = props::conformable(aref); + if (!fits) { + return false; // Incompatible dimensions + } + if (!fits.template stride_compatible()) { + need_copy = true; + } else { + copy_or_ref = std::move(aref); + } + } else { + need_copy = true; + } + } + + if (need_copy) { + // We need to copy: If we need a mutable reference, or we're not supposed to convert + // (either because we're in the no-convert overload pass, or because we're explicitly + // instructed not to copy (via `py::arg().noconvert()`) we have to fail loading. + if (!convert || need_writeable) { + return false; + } + + Array copy = Array::ensure(src); + if (!copy) { + return false; + } + fits = props::conformable(copy); + if (!fits || !fits.template stride_compatible()) { + return false; + } + copy_or_ref = std::move(copy); + loader_life_support::add_patient(copy_or_ref); + } + + ref.reset(); + map.reset(new MapType(data(copy_or_ref), + fits.rows, + fits.cols, + make_stride(fits.stride.outer(), fits.stride.inner()))); + ref.reset(new Type(*map)); + + return true; + } + + // NOLINTNEXTLINE(google-explicit-constructor) + operator Type *() { return ref.get(); } + // NOLINTNEXTLINE(google-explicit-constructor) + operator Type &() { return *ref; } + template + using cast_op_type = pybind11::detail::cast_op_type<_T>; + +private: + template ::value, int> = 0> + Scalar *data(Array &a) { + return a.mutable_data(); + } + + template ::value, int> = 0> + const Scalar *data(Array &a) { + return a.data(); + } + + // Attempt to figure out a constructor of `Stride` that will work. + // If both strides are fixed, use a default constructor: + template + using stride_ctor_default = bool_constant::value>; + // Otherwise, if there is a two-index constructor, assume it is (outer,inner) like + // Eigen::Stride, and use it: + template + using stride_ctor_dual + = bool_constant::value + && std::is_constructible::value>; + // Otherwise, if there is a one-index constructor, and just one of the strides is dynamic, use + // it (passing whichever stride is dynamic). + template + using stride_ctor_outer + = bool_constant, stride_ctor_dual>::value + && S::OuterStrideAtCompileTime == Eigen::Dynamic + && S::InnerStrideAtCompileTime != Eigen::Dynamic + && std::is_constructible::value>; + template + using stride_ctor_inner + = bool_constant, stride_ctor_dual>::value + && S::InnerStrideAtCompileTime == Eigen::Dynamic + && S::OuterStrideAtCompileTime != Eigen::Dynamic + && std::is_constructible::value>; + + template ::value, int> = 0> + static S make_stride(EigenIndex, EigenIndex) { + return S(); + } + template ::value, int> = 0> + static S make_stride(EigenIndex outer, EigenIndex inner) { + return S(outer, inner); + } + template ::value, int> = 0> + static S make_stride(EigenIndex outer, EigenIndex) { + return S(outer); + } + template ::value, int> = 0> + static S make_stride(EigenIndex, EigenIndex inner) { + return S(inner); + } +}; + +// type_caster for special matrix types (e.g. DiagonalMatrix), which are EigenBase, but not +// EigenDense (i.e. they don't have a data(), at least not with the usual matrix layout). +// load() is not supported, but we can cast them into the python domain by first copying to a +// regular Eigen::Matrix, then casting that. +template +struct type_caster::value>> { + static_assert(!std::is_pointer::value, + PYBIND11_EIGEN_MESSAGE_POINTER_TYPES_ARE_NOT_SUPPORTED); + +protected: + using Matrix + = Eigen::Matrix; + using props = EigenProps; + +public: + static handle cast(const Type &src, return_value_policy /* policy */, handle /* parent */) { + handle h = eigen_encapsulate(new Matrix(src)); + return h; + } + static handle cast(const Type *src, return_value_policy policy, handle parent) { + return cast(*src, policy, parent); + } + + static constexpr auto name = props::descriptor; + + // Explicitly delete these: support python -> C++ conversion on these (i.e. these can be return + // types but not bound arguments). We still provide them (with an explicitly delete) so that + // you end up here if you try anyway. + bool load(handle, bool) = delete; + operator Type() = delete; + template + using cast_op_type = Type; +}; + +template +struct type_caster::value>> { + using Scalar = typename Type::Scalar; + static_assert(!std::is_pointer::value, + PYBIND11_EIGEN_MESSAGE_POINTER_TYPES_ARE_NOT_SUPPORTED); + using StorageIndex = remove_reference_t().outerIndexPtr())>; + using Index = typename Type::Index; + static constexpr bool rowMajor = Type::IsRowMajor; + + bool load(handle src, bool) { + if (!src) { + return false; + } + + auto obj = reinterpret_borrow(src); + object sparse_module = module_::import("scipy.sparse"); + object matrix_type = sparse_module.attr(rowMajor ? "csr_matrix" : "csc_matrix"); + + if (!type::handle_of(obj).is(matrix_type)) { + try { + obj = matrix_type(obj); + } catch (const error_already_set &) { + return false; + } + } + + auto values = array_t((object) obj.attr("data")); + auto innerIndices = array_t((object) obj.attr("indices")); + auto outerIndices = array_t((object) obj.attr("indptr")); + auto shape = pybind11::tuple((pybind11::object) obj.attr("shape")); + auto nnz = obj.attr("nnz").cast(); + + if (!values || !innerIndices || !outerIndices) { + return false; + } + + value = EigenMapSparseMatrix(shape[0].cast(), + shape[1].cast(), + std::move(nnz), + outerIndices.mutable_data(), + innerIndices.mutable_data(), + values.mutable_data()); + + return true; + } + + static handle cast(const Type &src, return_value_policy /* policy */, handle /* parent */) { + const_cast(src).makeCompressed(); + + object matrix_type + = module_::import("scipy.sparse").attr(rowMajor ? "csr_matrix" : "csc_matrix"); + + array data(src.nonZeros(), src.valuePtr()); + array outerIndices((rowMajor ? src.rows() : src.cols()) + 1, src.outerIndexPtr()); + array innerIndices(src.nonZeros(), src.innerIndexPtr()); + + return matrix_type(pybind11::make_tuple( + std::move(data), std::move(innerIndices), std::move(outerIndices)), + pybind11::make_tuple(src.rows(), src.cols())) + .release(); + } + + PYBIND11_TYPE_CASTER(Type, + const_name<(Type::IsRowMajor) != 0>("scipy.sparse.csr_matrix[", + "scipy.sparse.csc_matrix[") + + npy_format_descriptor::name + const_name("]")); +}; + +PYBIND11_NAMESPACE_END(detail) +PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE) diff --git a/pybind11/include/pybind11/eigen/tensor.h b/pybind11/include/pybind11/eigen/tensor.h new file mode 100644 index 000000000..25d12baca --- /dev/null +++ b/pybind11/include/pybind11/eigen/tensor.h @@ -0,0 +1,516 @@ +/* + pybind11/eigen/tensor.h: Transparent conversion for Eigen tensors + + All rights reserved. Use of this source code is governed by a + BSD-style license that can be found in the LICENSE file. +*/ + +#pragma once + +#include "../numpy.h" +#include "common.h" + +#if defined(__GNUC__) && !defined(__clang__) && !defined(__INTEL_COMPILER) +static_assert(__GNUC__ > 5, "Eigen Tensor support in pybind11 requires GCC > 5.0"); +#endif + +// Disable warnings for Eigen +PYBIND11_WARNING_PUSH +PYBIND11_WARNING_DISABLE_MSVC(4554) +PYBIND11_WARNING_DISABLE_MSVC(4127) +#if defined(__MINGW32__) +PYBIND11_WARNING_DISABLE_GCC("-Wmaybe-uninitialized") +#endif + +#include + +PYBIND11_WARNING_POP + +static_assert(EIGEN_VERSION_AT_LEAST(3, 3, 0), + "Eigen Tensor support in pybind11 requires Eigen >= 3.3.0"); + +PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE) + +PYBIND11_WARNING_DISABLE_MSVC(4127) + +PYBIND11_NAMESPACE_BEGIN(detail) + +inline bool is_tensor_aligned(const void *data) { + return (reinterpret_cast(data) % EIGEN_DEFAULT_ALIGN_BYTES) == 0; +} + +template +constexpr int compute_array_flag_from_tensor() { + static_assert((static_cast(T::Layout) == static_cast(Eigen::RowMajor)) + || (static_cast(T::Layout) == static_cast(Eigen::ColMajor)), + "Layout must be row or column major"); + return (static_cast(T::Layout) == static_cast(Eigen::RowMajor)) ? array::c_style + : array::f_style; +} + +template +struct eigen_tensor_helper {}; + +template +struct eigen_tensor_helper> { + using Type = Eigen::Tensor; + using ValidType = void; + + static Eigen::DSizes get_shape(const Type &f) { + return f.dimensions(); + } + + static constexpr bool + is_correct_shape(const Eigen::DSizes & /*shape*/) { + return true; + } + + template + struct helper {}; + + template + struct helper> { + static constexpr auto value = concat(const_name(((void) Is, "?"))...); + }; + + static constexpr auto dimensions_descriptor + = helper())>::value; + + template + static Type *alloc(Args &&...args) { + return new Type(std::forward(args)...); + } + + static void free(Type *tensor) { delete tensor; } +}; + +template +struct eigen_tensor_helper< + Eigen::TensorFixedSize, Options_, IndexType>> { + using Type = Eigen::TensorFixedSize, Options_, IndexType>; + using ValidType = void; + + static constexpr Eigen::DSizes + get_shape(const Type & /*f*/) { + return get_shape(); + } + + static constexpr Eigen::DSizes get_shape() { + return Eigen::DSizes(Indices...); + } + + static bool + is_correct_shape(const Eigen::DSizes &shape) { + return get_shape() == shape; + } + + static constexpr auto dimensions_descriptor = concat(const_name()...); + + template + static Type *alloc(Args &&...args) { + Eigen::aligned_allocator allocator; + return ::new (allocator.allocate(1)) Type(std::forward(args)...); + } + + static void free(Type *tensor) { + Eigen::aligned_allocator allocator; + tensor->~Type(); + allocator.deallocate(tensor, 1); + } +}; + +template +struct get_tensor_descriptor { + static constexpr auto details + = const_name(", flags.writeable", "") + + const_name(Type::Layout) == static_cast(Eigen::RowMajor)>( + ", flags.c_contiguous", ", flags.f_contiguous"); + static constexpr auto value + = const_name("numpy.ndarray[") + npy_format_descriptor::name + + const_name("[") + eigen_tensor_helper>::dimensions_descriptor + + const_name("]") + const_name(details, const_name("")) + const_name("]"); +}; + +// When EIGEN_AVOID_STL_ARRAY is defined, Eigen::DSizes does not have the begin() member +// function. Falling back to a simple loop works around this issue. +// +// We need to disable the type-limits warning for the inner loop when size = 0. + +PYBIND11_WARNING_PUSH +PYBIND11_WARNING_DISABLE_GCC("-Wtype-limits") + +template +std::vector convert_dsizes_to_vector(const Eigen::DSizes &arr) { + std::vector result(size); + + for (size_t i = 0; i < size; i++) { + result[i] = arr[i]; + } + + return result; +} + +template +Eigen::DSizes get_shape_for_array(const array &arr) { + Eigen::DSizes result; + const T *shape = arr.shape(); + for (size_t i = 0; i < size; i++) { + result[i] = shape[i]; + } + + return result; +} + +PYBIND11_WARNING_POP + +template +struct type_caster::ValidType> { + static_assert(!std::is_pointer::value, + PYBIND11_EIGEN_MESSAGE_POINTER_TYPES_ARE_NOT_SUPPORTED); + using Helper = eigen_tensor_helper; + static constexpr auto temp_name = get_tensor_descriptor::value; + PYBIND11_TYPE_CASTER(Type, temp_name); + + bool load(handle src, bool convert) { + if (!convert) { + if (!isinstance(src)) { + return false; + } + array temp = array::ensure(src); + if (!temp) { + return false; + } + + if (!temp.dtype().is(dtype::of())) { + return false; + } + } + + array_t()> arr( + reinterpret_borrow(src)); + + if (arr.ndim() != Type::NumIndices) { + return false; + } + auto shape = get_shape_for_array(arr); + + if (!Helper::is_correct_shape(shape)) { + return false; + } + +#if EIGEN_VERSION_AT_LEAST(3, 4, 0) + auto data_pointer = arr.data(); +#else + // Handle Eigen bug + auto data_pointer = const_cast(arr.data()); +#endif + + if (is_tensor_aligned(arr.data())) { + value = Eigen::TensorMap(data_pointer, shape); + } else { + value = Eigen::TensorMap(data_pointer, shape); + } + + return true; + } + + static handle cast(Type &&src, return_value_policy policy, handle parent) { + if (policy == return_value_policy::reference + || policy == return_value_policy::reference_internal) { + pybind11_fail("Cannot use a reference return value policy for an rvalue"); + } + return cast_impl(&src, return_value_policy::move, parent); + } + + static handle cast(const Type &&src, return_value_policy policy, handle parent) { + if (policy == return_value_policy::reference + || policy == return_value_policy::reference_internal) { + pybind11_fail("Cannot use a reference return value policy for an rvalue"); + } + return cast_impl(&src, return_value_policy::move, parent); + } + + static handle cast(Type &src, return_value_policy policy, handle parent) { + if (policy == return_value_policy::automatic + || policy == return_value_policy::automatic_reference) { + policy = return_value_policy::copy; + } + return cast_impl(&src, policy, parent); + } + + static handle cast(const Type &src, return_value_policy policy, handle parent) { + if (policy == return_value_policy::automatic + || policy == return_value_policy::automatic_reference) { + policy = return_value_policy::copy; + } + return cast(&src, policy, parent); + } + + static handle cast(Type *src, return_value_policy policy, handle parent) { + if (policy == return_value_policy::automatic) { + policy = return_value_policy::take_ownership; + } else if (policy == return_value_policy::automatic_reference) { + policy = return_value_policy::reference; + } + return cast_impl(src, policy, parent); + } + + static handle cast(const Type *src, return_value_policy policy, handle parent) { + if (policy == return_value_policy::automatic) { + policy = return_value_policy::take_ownership; + } else if (policy == return_value_policy::automatic_reference) { + policy = return_value_policy::reference; + } + return cast_impl(src, policy, parent); + } + + template + static handle cast_impl(C *src, return_value_policy policy, handle parent) { + object parent_object; + bool writeable = false; + switch (policy) { + case return_value_policy::move: + if (std::is_const::value) { + pybind11_fail("Cannot move from a constant reference"); + } + + src = Helper::alloc(std::move(*src)); + + parent_object + = capsule(src, [](void *ptr) { Helper::free(reinterpret_cast(ptr)); }); + writeable = true; + break; + + case return_value_policy::take_ownership: + if (std::is_const::value) { + // This cast is ugly, and might be UB in some cases, but we don't have an + // alternative here as we must free that memory + Helper::free(const_cast(src)); + pybind11_fail("Cannot take ownership of a const reference"); + } + + parent_object + = capsule(src, [](void *ptr) { Helper::free(reinterpret_cast(ptr)); }); + writeable = true; + break; + + case return_value_policy::copy: + writeable = true; + break; + + case return_value_policy::reference: + parent_object = none(); + writeable = !std::is_const::value; + break; + + case return_value_policy::reference_internal: + // Default should do the right thing + if (!parent) { + pybind11_fail("Cannot use reference internal when there is no parent"); + } + parent_object = reinterpret_borrow(parent); + writeable = !std::is_const::value; + break; + + default: + pybind11_fail("pybind11 bug in eigen.h, please file a bug report"); + } + + auto result = array_t()>( + convert_dsizes_to_vector(Helper::get_shape(*src)), src->data(), parent_object); + + if (!writeable) { + array_proxy(result.ptr())->flags &= ~detail::npy_api::NPY_ARRAY_WRITEABLE_; + } + + return result.release(); + } +}; + +template = true> +StoragePointerType get_array_data_for_type(array &arr) { +#if EIGEN_VERSION_AT_LEAST(3, 4, 0) + return reinterpret_cast(arr.data()); +#else + // Handle Eigen bug + return reinterpret_cast(const_cast(arr.data())); +#endif +} + +template = true> +StoragePointerType get_array_data_for_type(array &arr) { + return reinterpret_cast(arr.mutable_data()); +} + +template +struct get_storage_pointer_type; + +template +struct get_storage_pointer_type> { + using SPT = typename MapType::StoragePointerType; +}; + +template +struct get_storage_pointer_type> { + using SPT = typename MapType::PointerArgType; +}; + +template +struct type_caster, + typename eigen_tensor_helper>::ValidType> { + static_assert(!std::is_pointer::value, + PYBIND11_EIGEN_MESSAGE_POINTER_TYPES_ARE_NOT_SUPPORTED); + using MapType = Eigen::TensorMap; + using Helper = eigen_tensor_helper>; + + bool load(handle src, bool /*convert*/) { + // Note that we have a lot more checks here as we want to make sure to avoid copies + if (!isinstance(src)) { + return false; + } + auto arr = reinterpret_borrow(src); + if ((arr.flags() & compute_array_flag_from_tensor()) == 0) { + return false; + } + + if (!arr.dtype().is(dtype::of())) { + return false; + } + + if (arr.ndim() != Type::NumIndices) { + return false; + } + + constexpr bool is_aligned = (Options & Eigen::Aligned) != 0; + + if (is_aligned && !is_tensor_aligned(arr.data())) { + return false; + } + + auto shape = get_shape_for_array(arr); + + if (!Helper::is_correct_shape(shape)) { + return false; + } + + if (needs_writeable && !arr.writeable()) { + return false; + } + + auto result = get_array_data_for_type::SPT, + needs_writeable>(arr); + + value.reset(new MapType(std::move(result), std::move(shape))); + + return true; + } + + static handle cast(MapType &&src, return_value_policy policy, handle parent) { + return cast_impl(&src, policy, parent); + } + + static handle cast(const MapType &&src, return_value_policy policy, handle parent) { + return cast_impl(&src, policy, parent); + } + + static handle cast(MapType &src, return_value_policy policy, handle parent) { + if (policy == return_value_policy::automatic + || policy == return_value_policy::automatic_reference) { + policy = return_value_policy::copy; + } + return cast_impl(&src, policy, parent); + } + + static handle cast(const MapType &src, return_value_policy policy, handle parent) { + if (policy == return_value_policy::automatic + || policy == return_value_policy::automatic_reference) { + policy = return_value_policy::copy; + } + return cast(&src, policy, parent); + } + + static handle cast(MapType *src, return_value_policy policy, handle parent) { + if (policy == return_value_policy::automatic) { + policy = return_value_policy::take_ownership; + } else if (policy == return_value_policy::automatic_reference) { + policy = return_value_policy::reference; + } + return cast_impl(src, policy, parent); + } + + static handle cast(const MapType *src, return_value_policy policy, handle parent) { + if (policy == return_value_policy::automatic) { + policy = return_value_policy::take_ownership; + } else if (policy == return_value_policy::automatic_reference) { + policy = return_value_policy::reference; + } + return cast_impl(src, policy, parent); + } + + template + static handle cast_impl(C *src, return_value_policy policy, handle parent) { + object parent_object; + constexpr bool writeable = !std::is_const::value; + switch (policy) { + case return_value_policy::reference: + parent_object = none(); + break; + + case return_value_policy::reference_internal: + // Default should do the right thing + if (!parent) { + pybind11_fail("Cannot use reference internal when there is no parent"); + } + parent_object = reinterpret_borrow(parent); + break; + + case return_value_policy::take_ownership: + delete src; + // fallthrough + default: + // move, take_ownership don't make any sense for a ref/map: + pybind11_fail("Invalid return_value_policy for Eigen Map type, must be either " + "reference or reference_internal"); + } + + auto result = array_t()>( + convert_dsizes_to_vector(Helper::get_shape(*src)), + src->data(), + std::move(parent_object)); + + if (!writeable) { + array_proxy(result.ptr())->flags &= ~detail::npy_api::NPY_ARRAY_WRITEABLE_; + } + + return result.release(); + } + +#if EIGEN_VERSION_AT_LEAST(3, 4, 0) + + static constexpr bool needs_writeable = !std::is_const::SPT>::type>::value; +#else + // Handle Eigen bug + static constexpr bool needs_writeable = !std::is_const::value; +#endif + +protected: + // TODO: Move to std::optional once std::optional has more support + std::unique_ptr value; + +public: + static constexpr auto name = get_tensor_descriptor::value; + explicit operator MapType *() { return value.get(); } + explicit operator MapType &() { return *value; } + explicit operator MapType &&() && { return std::move(*value); } + + template + using cast_op_type = ::pybind11::detail::movable_cast_op_type; +}; + +PYBIND11_NAMESPACE_END(detail) +PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE) diff --git a/pybind11/include/pybind11/embed.h b/pybind11/include/pybind11/embed.h index d6999cd77..caa14f4a0 100644 --- a/pybind11/include/pybind11/embed.h +++ b/pybind11/include/pybind11/embed.h @@ -86,38 +86,26 @@ inline wchar_t *widen_chars(const char *safe_arg) { return widened_arg; } -PYBIND11_NAMESPACE_END(detail) - -/** \rst - Initialize the Python interpreter. No other pybind11 or CPython API functions can be - called before this is done; with the exception of `PYBIND11_EMBEDDED_MODULE`. The - optional `init_signal_handlers` parameter can be used to skip the registration of - signal handlers (see the `Python documentation`_ for details). Calling this function - again after the interpreter has already been initialized is a fatal error. - - If initializing the Python interpreter fails, then the program is terminated. (This - is controlled by the CPython runtime and is an exception to pybind11's normal behavior - of throwing exceptions on errors.) - - The remaining optional parameters, `argc`, `argv`, and `add_program_dir_to_path` are - used to populate ``sys.argv`` and ``sys.path``. - See the |PySys_SetArgvEx documentation|_ for details. - - .. _Python documentation: https://docs.python.org/3/c-api/init.html#c.Py_InitializeEx - .. |PySys_SetArgvEx documentation| replace:: ``PySys_SetArgvEx`` documentation - .. _PySys_SetArgvEx documentation: https://docs.python.org/3/c-api/init.html#c.PySys_SetArgvEx - \endrst */ -inline void initialize_interpreter(bool init_signal_handlers = true, - int argc = 0, - const char *const *argv = nullptr, - bool add_program_dir_to_path = true) { +inline void precheck_interpreter() { if (Py_IsInitialized() != 0) { pybind11_fail("The interpreter is already running"); } +} -#if PY_VERSION_HEX < 0x030B0000 +#if !defined(PYBIND11_PYCONFIG_SUPPORT_PY_VERSION_HEX) +# define PYBIND11_PYCONFIG_SUPPORT_PY_VERSION_HEX (0x03080000) +#endif +#if PY_VERSION_HEX < PYBIND11_PYCONFIG_SUPPORT_PY_VERSION_HEX +inline void initialize_interpreter_pre_pyconfig(bool init_signal_handlers, + int argc, + const char *const *argv, + bool add_program_dir_to_path) { + detail::precheck_interpreter(); Py_InitializeEx(init_signal_handlers ? 1 : 0); +# if defined(WITH_THREAD) && PY_VERSION_HEX < 0x03070000 + PyEval_InitThreads(); +# endif // Before it was special-cased in python 3.8, passing an empty or null argv // caused a segfault, so we have to reimplement the special case ourselves. @@ -147,24 +135,30 @@ inline void initialize_interpreter(bool init_signal_handlers = true, auto *pysys_argv = widened_argv.get(); PySys_SetArgvEx(argc, pysys_argv, static_cast(add_program_dir_to_path)); -#else - PyConfig config; - PyConfig_InitIsolatedConfig(&config); - config.install_signal_handlers = init_signal_handlers ? 1 : 0; +} +#endif - PyStatus status = PyConfig_SetBytesArgv(&config, argc, const_cast(argv)); - if (PyStatus_Exception(status)) { +PYBIND11_NAMESPACE_END(detail) + +#if PY_VERSION_HEX >= PYBIND11_PYCONFIG_SUPPORT_PY_VERSION_HEX +inline void initialize_interpreter(PyConfig *config, + int argc = 0, + const char *const *argv = nullptr, + bool add_program_dir_to_path = true) { + detail::precheck_interpreter(); + PyStatus status = PyConfig_SetBytesArgv(config, argc, const_cast(argv)); + if (PyStatus_Exception(status) != 0) { // A failure here indicates a character-encoding failure or the python // interpreter out of memory. Give up. - PyConfig_Clear(&config); - throw std::runtime_error(PyStatus_IsError(status) ? status.err_msg - : "Failed to prepare CPython"); + PyConfig_Clear(config); + throw std::runtime_error(PyStatus_IsError(status) != 0 ? status.err_msg + : "Failed to prepare CPython"); } - status = Py_InitializeFromConfig(&config); - PyConfig_Clear(&config); - if (PyStatus_Exception(status)) { - throw std::runtime_error(PyStatus_IsError(status) ? status.err_msg - : "Failed to init CPython"); + status = Py_InitializeFromConfig(config); + if (PyStatus_Exception(status) != 0) { + PyConfig_Clear(config); + throw std::runtime_error(PyStatus_IsError(status) != 0 ? status.err_msg + : "Failed to init CPython"); } if (add_program_dir_to_path) { PyRun_SimpleString("import sys, os.path; " @@ -172,6 +166,44 @@ inline void initialize_interpreter(bool init_signal_handlers = true, "os.path.abspath(os.path.dirname(sys.argv[0])) " "if sys.argv and os.path.exists(sys.argv[0]) else '')"); } + PyConfig_Clear(config); +} +#endif + +/** \rst + Initialize the Python interpreter. No other pybind11 or CPython API functions can be + called before this is done; with the exception of `PYBIND11_EMBEDDED_MODULE`. The + optional `init_signal_handlers` parameter can be used to skip the registration of + signal handlers (see the `Python documentation`_ for details). Calling this function + again after the interpreter has already been initialized is a fatal error. + + If initializing the Python interpreter fails, then the program is terminated. (This + is controlled by the CPython runtime and is an exception to pybind11's normal behavior + of throwing exceptions on errors.) + + The remaining optional parameters, `argc`, `argv`, and `add_program_dir_to_path` are + used to populate ``sys.argv`` and ``sys.path``. + See the |PySys_SetArgvEx documentation|_ for details. + + .. _Python documentation: https://docs.python.org/3/c-api/init.html#c.Py_InitializeEx + .. |PySys_SetArgvEx documentation| replace:: ``PySys_SetArgvEx`` documentation + .. _PySys_SetArgvEx documentation: https://docs.python.org/3/c-api/init.html#c.PySys_SetArgvEx + \endrst */ +inline void initialize_interpreter(bool init_signal_handlers = true, + int argc = 0, + const char *const *argv = nullptr, + bool add_program_dir_to_path = true) { +#if PY_VERSION_HEX < PYBIND11_PYCONFIG_SUPPORT_PY_VERSION_HEX + detail::initialize_interpreter_pre_pyconfig( + init_signal_handlers, argc, argv, add_program_dir_to_path); +#else + PyConfig config; + PyConfig_InitPythonConfig(&config); + // See PR #4473 for background + config.parse_argv = 0; + + config.install_signal_handlers = init_signal_handlers ? 1 : 0; + initialize_interpreter(&config, argc, argv, add_program_dir_to_path); #endif } @@ -211,16 +243,14 @@ inline void initialize_interpreter(bool init_signal_handlers = true, \endrst */ inline void finalize_interpreter() { - handle builtins(PyEval_GetBuiltins()); - const char *id = PYBIND11_INTERNALS_ID; - // Get the internals pointer (without creating it if it doesn't exist). It's possible for the // internals to be created during Py_Finalize() (e.g. if a py::capsule calls `get_internals()` // during destruction), so we get the pointer-pointer here and check it after Py_Finalize(). detail::internals **internals_ptr_ptr = detail::get_internals_pp(); - // It could also be stashed in builtins, so look there too: - if (builtins.contains(id) && isinstance(builtins[id])) { - internals_ptr_ptr = capsule(builtins[id]); + // It could also be stashed in state_dict, so look there too: + if (object internals_obj + = get_internals_obj_from_state_dict(detail::get_python_state_dict())) { + internals_ptr_ptr = detail::get_internals_pp_from_capsule(internals_obj); } // Local internals contains data managed by the current interpreter, so we must clear them to // avoid undefined behaviors when initializing another interpreter @@ -259,6 +289,15 @@ public: initialize_interpreter(init_signal_handlers, argc, argv, add_program_dir_to_path); } +#if PY_VERSION_HEX >= PYBIND11_PYCONFIG_SUPPORT_PY_VERSION_HEX + explicit scoped_interpreter(PyConfig *config, + int argc = 0, + const char *const *argv = nullptr, + bool add_program_dir_to_path = true) { + initialize_interpreter(config, argc, argv, add_program_dir_to_path); + } +#endif + scoped_interpreter(const scoped_interpreter &) = delete; scoped_interpreter(scoped_interpreter &&other) noexcept { other.is_valid = false; } scoped_interpreter &operator=(const scoped_interpreter &) = delete; diff --git a/pybind11/include/pybind11/functional.h b/pybind11/include/pybind11/functional.h index 4034990d8..87ec4d10c 100644 --- a/pybind11/include/pybind11/functional.h +++ b/pybind11/include/pybind11/functional.h @@ -48,9 +48,16 @@ public: */ if (auto cfunc = func.cpp_function()) { auto *cfunc_self = PyCFunction_GET_SELF(cfunc.ptr()); - if (isinstance(cfunc_self)) { + if (cfunc_self == nullptr) { + PyErr_Clear(); + } else if (isinstance(cfunc_self)) { auto c = reinterpret_borrow(cfunc_self); - auto *rec = (function_record *) c; + + function_record *rec = nullptr; + // Check that we can safely reinterpret the capsule into a function_record + if (detail::is_function_record_capsule(c)) { + rec = c.get_pointer(); + } while (rec != nullptr) { if (rec->is_stateless @@ -110,7 +117,7 @@ public: template static handle cast(Func &&f_, return_value_policy policy, handle /* parent */) { if (!f_) { - return none().inc_ref(); + return none().release(); } auto result = f_.template target(); diff --git a/pybind11/include/pybind11/gil.h b/pybind11/include/pybind11/gil.h index a0b5de151..570a5581d 100644 --- a/pybind11/include/pybind11/gil.h +++ b/pybind11/include/pybind11/gil.h @@ -10,7 +10,10 @@ #pragma once #include "detail/common.h" -#include "detail/internals.h" + +#if defined(WITH_THREAD) && !defined(PYBIND11_SIMPLE_GIL_MANAGEMENT) +# include "detail/internals.h" +#endif PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE) @@ -21,7 +24,9 @@ PyThreadState *get_thread_state_unchecked(); PYBIND11_NAMESPACE_END(detail) -#if defined(WITH_THREAD) && !defined(PYPY_VERSION) +#if defined(WITH_THREAD) + +# if !defined(PYBIND11_SIMPLE_GIL_MANAGEMENT) /* The functions below essentially reproduce the PyGILState_* API using a RAII * pattern, but there are a few important differences: @@ -62,11 +67,11 @@ public: if (!tstate) { tstate = PyThreadState_New(internals.istate); -# if defined(PYBIND11_DETAILED_ERROR_MESSAGES) +# if defined(PYBIND11_DETAILED_ERROR_MESSAGES) if (!tstate) { pybind11_fail("scoped_acquire: could not create thread state!"); } -# endif +# endif tstate->gilstate_counter = 0; PYBIND11_TLS_REPLACE_VALUE(internals.tstate, tstate); } else { @@ -80,24 +85,27 @@ public: inc_ref(); } + gil_scoped_acquire(const gil_scoped_acquire &) = delete; + gil_scoped_acquire &operator=(const gil_scoped_acquire &) = delete; + void inc_ref() { ++tstate->gilstate_counter; } PYBIND11_NOINLINE void dec_ref() { --tstate->gilstate_counter; -# if defined(PYBIND11_DETAILED_ERROR_MESSAGES) +# if defined(PYBIND11_DETAILED_ERROR_MESSAGES) if (detail::get_thread_state_unchecked() != tstate) { pybind11_fail("scoped_acquire::dec_ref(): thread state must be current!"); } if (tstate->gilstate_counter < 0) { pybind11_fail("scoped_acquire::dec_ref(): reference count underflow!"); } -# endif +# endif if (tstate->gilstate_counter == 0) { -# if defined(PYBIND11_DETAILED_ERROR_MESSAGES) +# if defined(PYBIND11_DETAILED_ERROR_MESSAGES) if (!release) { pybind11_fail("scoped_acquire::dec_ref(): internal error!"); } -# endif +# endif PyThreadState_Clear(tstate); if (active) { PyThreadState_DeleteCurrent(); @@ -144,6 +152,9 @@ public: } } + gil_scoped_release(const gil_scoped_release &) = delete; + gil_scoped_release &operator=(const gil_scoped_release &) = delete; + /// This method will disable the PyThreadState_DeleteCurrent call and the /// GIL won't be acquired. This method should be used if the interpreter /// could be shutting down when this is called, as thread deletion is not @@ -172,12 +183,16 @@ private: bool disassoc; bool active = true; }; -#elif defined(PYPY_VERSION) + +# else // PYBIND11_SIMPLE_GIL_MANAGEMENT + class gil_scoped_acquire { PyGILState_STATE state; public: - gil_scoped_acquire() { state = PyGILState_Ensure(); } + gil_scoped_acquire() : state{PyGILState_Ensure()} {} + gil_scoped_acquire(const gil_scoped_acquire &) = delete; + gil_scoped_acquire &operator=(const gil_scoped_acquire &) = delete; ~gil_scoped_acquire() { PyGILState_Release(state); } void disarm() {} }; @@ -186,17 +201,39 @@ class gil_scoped_release { PyThreadState *state; public: - gil_scoped_release() { state = PyEval_SaveThread(); } + gil_scoped_release() : state{PyEval_SaveThread()} {} + gil_scoped_release(const gil_scoped_release &) = delete; + gil_scoped_release &operator=(const gil_scoped_release &) = delete; ~gil_scoped_release() { PyEval_RestoreThread(state); } void disarm() {} }; -#else + +# endif // PYBIND11_SIMPLE_GIL_MANAGEMENT + +#else // WITH_THREAD + class gil_scoped_acquire { +public: + gil_scoped_acquire() { + // Trick to suppress `unused variable` error messages (at call sites). + (void) (this != (this + 1)); + } + gil_scoped_acquire(const gil_scoped_acquire &) = delete; + gil_scoped_acquire &operator=(const gil_scoped_acquire &) = delete; void disarm() {} }; + class gil_scoped_release { +public: + gil_scoped_release() { + // Trick to suppress `unused variable` error messages (at call sites). + (void) (this != (this + 1)); + } + gil_scoped_release(const gil_scoped_release &) = delete; + gil_scoped_release &operator=(const gil_scoped_release &) = delete; void disarm() {} }; -#endif + +#endif // WITH_THREAD PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE) diff --git a/pybind11/include/pybind11/numpy.h b/pybind11/include/pybind11/numpy.h index 0291b02d0..36077ec04 100644 --- a/pybind11/include/pybind11/numpy.h +++ b/pybind11/include/pybind11/numpy.h @@ -36,6 +36,8 @@ static_assert(std::is_signed::value, "Py_intptr_t must be signed"); PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE) +PYBIND11_WARNING_DISABLE_MSVC(4127) + class array; // Forward declaration PYBIND11_NAMESPACE_BEGIN(detail) @@ -537,7 +539,7 @@ PYBIND11_NAMESPACE_END(detail) class dtype : public object { public: - PYBIND11_OBJECT_DEFAULT(dtype, object, detail::npy_api::get().PyArrayDescr_Check_); + PYBIND11_OBJECT_DEFAULT(dtype, object, detail::npy_api::get().PyArrayDescr_Check_) explicit dtype(const buffer_info &info) { dtype descr(_dtype_from_pep3118()(pybind11::str(info.format))); @@ -562,6 +564,8 @@ public: m_ptr = from_args(args).release().ptr(); } + /// Return dtype for the given typenum (one of the NPY_TYPES). + /// https://numpy.org/devdocs/reference/c-api/array.html#c.PyArray_DescrFromType explicit dtype(int typenum) : object(detail::npy_api::get().PyArray_DescrFromType_(typenum), stolen_t{}) { if (m_ptr == nullptr) { @@ -875,7 +879,7 @@ public: */ template detail::unchecked_mutable_reference mutable_unchecked() & { - if (PYBIND11_SILENCE_MSVC_C4127(Dims >= 0) && ndim() != Dims) { + if (Dims >= 0 && ndim() != Dims) { throw std::domain_error("array has incorrect number of dimensions: " + std::to_string(ndim()) + "; expected " + std::to_string(Dims)); @@ -893,7 +897,7 @@ public: */ template detail::unchecked_reference unchecked() const & { - if (PYBIND11_SILENCE_MSVC_C4127(Dims >= 0) && ndim() != Dims) { + if (Dims >= 0 && ndim() != Dims) { throw std::domain_error("array has incorrect number of dimensions: " + std::to_string(ndim()) + "; expected " + std::to_string(Dims)); @@ -1119,10 +1123,10 @@ public: /** * Returns a proxy object that provides const access to the array's data without bounds or - * dimensionality checking. Unlike `unchecked()`, this does not require that the underlying - * array have the `writable` flag. Use with care: the array must not be destroyed or reshaped - * for the duration of the returned object, and the caller must take care not to access invalid - * dimensions or dimension indices. + * dimensionality checking. Unlike `mutable_unchecked()`, this does not require that the + * underlying array have the `writable` flag. Use with care: the array must not be destroyed + * or reshaped for the duration of the returned object, and the caller must take care not to + * access invalid dimensions or dimension indices. */ template detail::unchecked_reference unchecked() const & { @@ -1281,12 +1285,16 @@ private: public: static constexpr int value = values[detail::is_fmt_numeric::index]; - static pybind11::dtype dtype() { - if (auto *ptr = npy_api::get().PyArray_DescrFromType_(value)) { - return reinterpret_steal(ptr); - } - pybind11_fail("Unsupported buffer format!"); - } + static pybind11::dtype dtype() { return pybind11::dtype(/*typenum*/ value); } +}; + +template +struct npy_format_descriptor::value>> { + static constexpr auto name = const_name("object"); + + static constexpr int value = npy_api::NPY_OBJECT_; + + static pybind11::dtype dtype() { return pybind11::dtype(/*typenum*/ value); } }; #define PYBIND11_DECL_CHAR_FMT \ @@ -1401,7 +1409,7 @@ PYBIND11_NOINLINE void register_structured_dtype(any_container oss << '}'; auto format_str = oss.str(); - // Sanity check: verify that NumPy properly parses our buffer format string + // Smoke test: verify that NumPy properly parses our buffer format string auto &api = npy_api::get(); auto arr = array(buffer_info(nullptr, itemsize, format_str, 1)); if (!api.PyArray_EquivTypes_(dtype_ptr, arr.dtype().ptr())) { @@ -1469,7 +1477,7 @@ private: } // Extract name, offset and format descriptor for a struct field -# define PYBIND11_FIELD_DESCRIPTOR(T, Field) PYBIND11_FIELD_DESCRIPTOR_EX(T, Field, # Field) +# define PYBIND11_FIELD_DESCRIPTOR(T, Field) PYBIND11_FIELD_DESCRIPTOR_EX(T, Field, #Field) // The main idea of this macro is borrowed from https://github.com/swansontec/map-macro // (C) William Swanson, Paul Fultz @@ -1866,8 +1874,13 @@ private: auto result = returned_array::create(trivial, shape); + PYBIND11_WARNING_PUSH +#ifdef PYBIND11_DETECTED_CLANG_WITH_MISLEADING_CALL_STD_MOVE_EXPLICITLY_WARNING + PYBIND11_WARNING_DISABLE_CLANG("-Wreturn-std-move") +#endif + if (size == 0) { - return std::move(result); + return result; } /* Call the function */ @@ -1878,7 +1891,8 @@ private: apply_trivial(buffers, params, mutable_data, size, i_seq, vi_seq, bi_seq); } - return std::move(result); + return result; + PYBIND11_WARNING_POP } template diff --git a/pybind11/include/pybind11/operators.h b/pybind11/include/pybind11/operators.h index a0c3b78d6..16a88ae17 100644 --- a/pybind11/include/pybind11/operators.h +++ b/pybind11/include/pybind11/operators.h @@ -84,6 +84,7 @@ struct op_impl {}; /// Operator implementation generator template struct op_ { + static constexpr bool op_enable_if_hook = true; template void execute(Class &cl, const Extra &...extra) const { using Base = typename Class::type; diff --git a/pybind11/include/pybind11/options.h b/pybind11/include/pybind11/options.h index 1e493bdcc..1b2122522 100644 --- a/pybind11/include/pybind11/options.h +++ b/pybind11/include/pybind11/options.h @@ -47,6 +47,16 @@ public: return *this; } + options &disable_enum_members_docstring() & { + global_state().show_enum_members_docstring = false; + return *this; + } + + options &enable_enum_members_docstring() & { + global_state().show_enum_members_docstring = true; + return *this; + } + // Getter methods (return the global state): static bool show_user_defined_docstrings() { @@ -55,6 +65,10 @@ public: static bool show_function_signatures() { return global_state().show_function_signatures; } + static bool show_enum_members_docstring() { + return global_state().show_enum_members_docstring; + } + // This type is not meant to be allocated on the heap. void *operator new(size_t) = delete; @@ -63,6 +77,8 @@ private: bool show_user_defined_docstrings = true; //< Include user-supplied texts in docstrings. bool show_function_signatures = true; //< Include auto-generated function signatures // in docstrings. + bool show_enum_members_docstring = true; //< Include auto-generated member list in enum + // docstrings. }; static state &global_state() { diff --git a/pybind11/include/pybind11/pybind11.h b/pybind11/include/pybind11/pybind11.h index d61dcd5c7..3bce1a01b 100644 --- a/pybind11/include/pybind11/pybind11.h +++ b/pybind11/include/pybind11/pybind11.h @@ -35,6 +35,8 @@ # include #endif +PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE) + /* https://stackoverflow.com/questions/46798456/handling-gccs-noexcept-type-warning This warning is about ABI compatibility, not code health. It is only actually needed in a couple places, but apparently GCC 7 "generates this warning if @@ -43,11 +45,10 @@ No other GCC version generates this warning. */ #if defined(__GNUC__) && __GNUC__ == 7 -# pragma GCC diagnostic push -# pragma GCC diagnostic ignored "-Wnoexcept-type" +PYBIND11_WARNING_DISABLE_GCC("-Wnoexcept-type") #endif -PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE) +PYBIND11_WARNING_DISABLE_MSVC(4127) PYBIND11_NAMESPACE_BEGIN(detail) @@ -83,6 +84,7 @@ public: cpp_function() = default; // NOLINTNEXTLINE(google-explicit-constructor) cpp_function(std::nullptr_t) {} + cpp_function(std::nullptr_t, const is_setter &) {} /// Construct a cpp_function from a vanilla function pointer template @@ -177,22 +179,22 @@ protected: auto *rec = unique_rec.get(); /* Store the capture object directly in the function record if there is enough space */ - if (PYBIND11_SILENCE_MSVC_C4127(sizeof(capture) <= sizeof(rec->data))) { + if (sizeof(capture) <= sizeof(rec->data)) { /* Without these pragmas, GCC warns that there might not be enough space to use the placement new operator. However, the 'if' statement above ensures that this is the case. */ -#if defined(__GNUG__) && __GNUC__ >= 6 && !defined(__clang__) && !defined(__INTEL_COMPILER) -# pragma GCC diagnostic push -# pragma GCC diagnostic ignored "-Wplacement-new" + PYBIND11_WARNING_PUSH + +#if defined(__GNUG__) && __GNUC__ >= 6 + PYBIND11_WARNING_DISABLE_GCC("-Wplacement-new") #endif + new ((capture *) &rec->data) capture{std::forward(f)}; -#if defined(__GNUG__) && __GNUC__ >= 6 && !defined(__clang__) && !defined(__INTEL_COMPILER) -# pragma GCC diagnostic pop -#endif -#if defined(__GNUG__) && !PYBIND11_HAS_STD_LAUNDER && !defined(__INTEL_COMPILER) -# pragma GCC diagnostic push -# pragma GCC diagnostic ignored "-Wstrict-aliasing" + +#if !PYBIND11_HAS_STD_LAUNDER + PYBIND11_WARNING_DISABLE_GCC("-Wstrict-aliasing") #endif + // UB without std::launder, but without breaking ABI and/or // a significant refactoring it's "impossible" to solve. if (!std::is_trivially_destructible::value) { @@ -202,9 +204,7 @@ protected: data->~capture(); }; } -#if defined(__GNUG__) && !PYBIND11_HAS_STD_LAUNDER && !defined(__INTEL_COMPILER) -# pragma GCC diagnostic pop -#endif + PYBIND11_WARNING_POP } else { rec->data[0] = new capture{std::forward(f)}; rec->free_data = [](function_record *r) { delete ((capture *) r->data[0]); }; @@ -245,10 +245,16 @@ protected: using Guard = extract_guard_t; /* Perform the function call */ - handle result - = cast_out::cast(std::move(args_converter).template call(cap->f), - policy, - call.parent); + handle result; + if (call.func.is_setter) { + (void) std::move(args_converter).template call(cap->f); + result = none().release(); + } else { + result = cast_out::cast( + std::move(args_converter).template call(cap->f), + policy, + call.parent); + } /* Invoke call policy post-call hook */ process_attributes::postcall(call, result); @@ -468,13 +474,20 @@ protected: if (rec->sibling) { if (PyCFunction_Check(rec->sibling.ptr())) { auto *self = PyCFunction_GET_SELF(rec->sibling.ptr()); - capsule rec_capsule = isinstance(self) ? reinterpret_borrow(self) - : capsule(self); - chain = (detail::function_record *) rec_capsule; - /* Never append a method to an overload chain of a parent class; - instead, hide the parent's overloads in this case */ - if (!chain->scope.is(rec->scope)) { + if (!isinstance(self)) { chain = nullptr; + } else { + auto rec_capsule = reinterpret_borrow(self); + if (detail::is_function_record_capsule(rec_capsule)) { + chain = rec_capsule.get_pointer(); + /* Never append a method to an overload chain of a parent class; + instead, hide the parent's overloads in this case */ + if (!chain->scope.is(rec->scope)) { + chain = nullptr; + } + } else { + chain = nullptr; + } } } // Don't trigger for things like the default __init__, which are wrapper_descriptors @@ -495,6 +508,7 @@ protected: rec->def->ml_flags = METH_VARARGS | METH_KEYWORDS; capsule rec_capsule(unique_rec.release(), + detail::get_function_record_capsule_name(), [](void *ptr) { destruct((detail::function_record *) ptr); }); guarded_strdup.release(); @@ -661,10 +675,13 @@ protected: /// Main dispatch logic for calls to functions bound using pybind11 static PyObject *dispatcher(PyObject *self, PyObject *args_in, PyObject *kwargs_in) { using namespace detail; + assert(isinstance(self)); /* Iterator over the list of potentially admissible overloads */ - const function_record *overloads = (function_record *) PyCapsule_GetPointer(self, nullptr), + const function_record *overloads = reinterpret_cast( + PyCapsule_GetPointer(self, get_function_record_capsule_name())), *it = overloads; + assert(overloads != nullptr); /* Need to know how many arguments + keyword arguments there are to pick the right overload */ @@ -1416,9 +1433,9 @@ template ::value, int> = 0> void call_operator_delete(T *p, size_t, size_t) { T::operator delete(p); } -template < - typename T, - enable_if_t::value && has_operator_delete_size::value, int> = 0> +template ::value && has_operator_delete_size::value, int> + = 0> void call_operator_delete(T *p, size_t s, size_t) { T::operator delete(p, s); } @@ -1578,14 +1595,14 @@ public: return *this; } - template - class_ &def(const detail::op_ &op, const Extra &...extra) { + template = 0> + class_ &def(const T &op, const Extra &...extra) { op.execute(*this, extra...); return *this; } - template - class_ &def_cast(const detail::op_ &op, const Extra &...extra) { + template = 0> + class_ &def_cast(const T &op, const Extra &...extra) { op.execute_cast(*this, extra...); return *this; } @@ -1719,7 +1736,8 @@ public: template class_ & def_property(const char *name, const Getter &fget, const Setter &fset, const Extra &...extra) { - return def_property(name, fget, cpp_function(method_adaptor(fset)), extra...); + return def_property( + name, fget, cpp_function(method_adaptor(fset), is_setter()), extra...); } template class_ &def_property(const char *name, @@ -1830,8 +1848,7 @@ private: if (holder_ptr) { init_holder_from_existing(v_h, holder_ptr, std::is_copy_constructible()); v_h.set_holder_constructed(); - } else if (PYBIND11_SILENCE_MSVC_C4127(detail::always_construct_holder::value) - || inst->owned) { + } else if (detail::always_construct_holder::value || inst->owned) { new (std::addressof(v_h.holder())) holder_type(v_h.value_ptr()); v_h.set_holder_constructed(); } @@ -1871,9 +1888,22 @@ private: static detail::function_record *get_function_record(handle h) { h = detail::get_function(h); - return h ? (detail::function_record *) reinterpret_borrow( - PyCFunction_GET_SELF(h.ptr())) - : nullptr; + if (!h) { + return nullptr; + } + + handle func_self = PyCFunction_GET_SELF(h.ptr()); + if (!func_self) { + throw error_already_set(); + } + if (!isinstance(func_self)) { + return nullptr; + } + auto cap = reinterpret_borrow(func_self); + if (!detail::is_function_record_capsule(cap)) { + return nullptr; + } + return cap.get_pointer(); } }; @@ -1950,29 +1980,35 @@ struct enum_base { name("name"), is_method(m_base)); - m_base.attr("__doc__") = static_property( - cpp_function( - [](handle arg) -> std::string { - std::string docstring; - dict entries = arg.attr("__entries"); - if (((PyTypeObject *) arg.ptr())->tp_doc) { - docstring += std::string(((PyTypeObject *) arg.ptr())->tp_doc) + "\n\n"; - } - docstring += "Members:"; - for (auto kv : entries) { - auto key = std::string(pybind11::str(kv.first)); - auto comment = kv.second[int_(1)]; - docstring += "\n\n " + key; - if (!comment.is_none()) { - docstring += " : " + (std::string) pybind11::str(comment); + if (options::show_enum_members_docstring()) { + m_base.attr("__doc__") = static_property( + cpp_function( + [](handle arg) -> std::string { + std::string docstring; + dict entries = arg.attr("__entries"); + if (((PyTypeObject *) arg.ptr())->tp_doc) { + docstring += std::string( + reinterpret_cast(arg.ptr())->tp_doc); + docstring += "\n\n"; } - } - return docstring; - }, - name("__doc__")), - none(), - none(), - ""); + docstring += "Members:"; + for (auto kv : entries) { + auto key = std::string(pybind11::str(kv.first)); + auto comment = kv.second[int_(1)]; + docstring += "\n\n "; + docstring += key; + if (!comment.is_none()) { + docstring += " : "; + docstring += pybind11::str(comment).cast(); + } + } + return docstring; + }, + name("__doc__")), + none(), + none(), + ""); + } m_base.attr("__members__") = static_property(cpp_function( [](handle arg) -> dict { @@ -2073,7 +2109,7 @@ struct enum_base { + "\" already exists!"); } - entries[name] = std::make_pair(value, doc); + entries[name] = pybind11::make_tuple(value, doc); m_base.attr(std::move(name)) = std::move(value); } @@ -2333,7 +2369,7 @@ template -iterator make_iterator_impl(Iterator &&first, Sentinel &&last, Extra &&...extra) { +iterator make_iterator_impl(Iterator first, Sentinel last, Extra &&...extra) { using state = detail::iterator_state; // TODO: state captures only the types of Extra, not the values @@ -2359,7 +2395,7 @@ iterator make_iterator_impl(Iterator &&first, Sentinel &&last, Extra &&...extra) Policy); } - return cast(state{std::forward(first), std::forward(last), true}); + return cast(state{first, last, true}); } PYBIND11_NAMESPACE_END(detail) @@ -2370,15 +2406,13 @@ template ::result_type, typename... Extra> -iterator make_iterator(Iterator &&first, Sentinel &&last, Extra &&...extra) { +iterator make_iterator(Iterator first, Sentinel last, Extra &&...extra) { return detail::make_iterator_impl, Policy, Iterator, Sentinel, ValueType, - Extra...>(std::forward(first), - std::forward(last), - std::forward(extra)...); + Extra...>(first, last, std::forward(extra)...); } /// Makes a python iterator over the keys (`.first`) of a iterator over pairs from a @@ -2388,15 +2422,13 @@ template ::result_type, typename... Extra> -iterator make_key_iterator(Iterator &&first, Sentinel &&last, Extra &&...extra) { +iterator make_key_iterator(Iterator first, Sentinel last, Extra &&...extra) { return detail::make_iterator_impl, Policy, Iterator, Sentinel, KeyType, - Extra...>(std::forward(first), - std::forward(last), - std::forward(extra)...); + Extra...>(first, last, std::forward(extra)...); } /// Makes a python iterator over the values (`.second`) of a iterator over pairs from a @@ -2406,15 +2438,13 @@ template ::result_type, typename... Extra> -iterator make_value_iterator(Iterator &&first, Sentinel &&last, Extra &&...extra) { +iterator make_value_iterator(Iterator first, Sentinel last, Extra &&...extra) { return detail::make_iterator_impl, Policy, Iterator, Sentinel, ValueType, - Extra...>(std::forward(first), - std::forward(last), - std::forward(extra)...); + Extra...>(first, last, std::forward(extra)...); } /// Makes an iterator over values of an stl container or other container supporting @@ -2858,7 +2888,3 @@ inline function get_overload(const T *this_ptr, const char *name) { PYBIND11_OVERRIDE_PURE(PYBIND11_TYPE(ret_type), PYBIND11_TYPE(cname), fn, __VA_ARGS__); PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE) - -#if defined(__GNUC__) && __GNUC__ == 7 -# pragma GCC diagnostic pop // -Wnoexcept-type -#endif diff --git a/pybind11/include/pybind11/pytypes.h b/pybind11/include/pybind11/pytypes.h index 339b0961e..64aad6347 100644 --- a/pybind11/include/pybind11/pytypes.h +++ b/pybind11/include/pybind11/pytypes.h @@ -33,6 +33,8 @@ PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE) +PYBIND11_WARNING_DISABLE_MSVC(4127) + /* A few forward declarations */ class handle; class object; @@ -155,23 +157,23 @@ public: object operator-() const; object operator~() const; object operator+(object_api const &other) const; - object operator+=(object_api const &other) const; + object operator+=(object_api const &other); object operator-(object_api const &other) const; - object operator-=(object_api const &other) const; + object operator-=(object_api const &other); object operator*(object_api const &other) const; - object operator*=(object_api const &other) const; + object operator*=(object_api const &other); object operator/(object_api const &other) const; - object operator/=(object_api const &other) const; + object operator/=(object_api const &other); object operator|(object_api const &other) const; - object operator|=(object_api const &other) const; + object operator|=(object_api const &other); object operator&(object_api const &other) const; - object operator&=(object_api const &other) const; + object operator&=(object_api const &other); object operator^(object_api const &other) const; - object operator^=(object_api const &other) const; + object operator^=(object_api const &other); object operator<<(object_api const &other) const; - object operator<<=(object_api const &other) const; + object operator<<=(object_api const &other); object operator>>(object_api const &other) const; - object operator>>=(object_api const &other) const; + object operator>>=(object_api const &other); PYBIND11_DEPRECATED("Use py::str(obj) instead") pybind11::str str() const; @@ -230,7 +232,8 @@ public: detail::enable_if_t, detail::is_pyobj_ptr_or_nullptr_t>, std::is_convertible>::value, - int> = 0> + int> + = 0> // NOLINTNEXTLINE(google-explicit-constructor) handle(T &obj) : m_ptr(obj) {} @@ -246,6 +249,11 @@ public: const handle &inc_ref() const & { #ifdef PYBIND11_HANDLE_REF_DEBUG inc_ref_counter(1); +#endif +#ifdef PYBIND11_ASSERT_GIL_HELD_INCREF_DECREF + if (m_ptr != nullptr && !PyGILState_Check()) { + throw_gilstate_error("pybind11::handle::inc_ref()"); + } #endif Py_XINCREF(m_ptr); return *this; @@ -257,6 +265,11 @@ public: this function automatically. Returns a reference to itself. \endrst */ const handle &dec_ref() const & { +#ifdef PYBIND11_ASSERT_GIL_HELD_INCREF_DECREF + if (m_ptr != nullptr && !PyGILState_Check()) { + throw_gilstate_error("pybind11::handle::dec_ref()"); + } +#endif Py_XDECREF(m_ptr); return *this; } @@ -283,8 +296,33 @@ public: protected: PyObject *m_ptr = nullptr; -#ifdef PYBIND11_HANDLE_REF_DEBUG private: +#ifdef PYBIND11_ASSERT_GIL_HELD_INCREF_DECREF + void throw_gilstate_error(const std::string &function_name) const { + fprintf( + stderr, + "%s is being called while the GIL is either not held or invalid. Please see " + "https://pybind11.readthedocs.io/en/stable/advanced/" + "misc.html#common-sources-of-global-interpreter-lock-errors for debugging advice.\n" + "If you are convinced there is no bug in your code, you can #define " + "PYBIND11_NO_ASSERT_GIL_HELD_INCREF_DECREF" + "to disable this check. In that case you have to ensure this #define is consistently " + "used for all translation units linked into a given pybind11 extension, otherwise " + "there will be ODR violations.", + function_name.c_str()); + fflush(stderr); + if (Py_TYPE(m_ptr)->tp_name != nullptr) { + fprintf(stderr, + "The failing %s call was triggered on a %s object.\n", + function_name.c_str(), + Py_TYPE(m_ptr)->tp_name); + fflush(stderr); + } + throw std::runtime_error(function_name + " PyGILState_Check() failure."); + } +#endif + +#ifdef PYBIND11_HANDLE_REF_DEBUG static std::size_t inc_ref_counter(std::size_t add) { thread_local std::size_t counter = 0; counter += add; @@ -334,12 +372,15 @@ public: } object &operator=(const object &other) { - other.inc_ref(); - // Use temporary variable to ensure `*this` remains valid while - // `Py_XDECREF` executes, in case `*this` is accessible from Python. - handle temp(m_ptr); - m_ptr = other.m_ptr; - temp.dec_ref(); + // Skip inc_ref and dec_ref if both objects are the same + if (!this->is(other)) { + other.inc_ref(); + // Use temporary variable to ensure `*this` remains valid while + // `Py_XDECREF` executes, in case `*this` is accessible from Python. + handle temp(m_ptr); + m_ptr = other.m_ptr; + temp.dec_ref(); + } return *this; } @@ -353,6 +394,20 @@ public: return *this; } +#define PYBIND11_INPLACE_OP(iop) \ + object iop(object_api const &other) { return operator=(handle::iop(other)); } + + PYBIND11_INPLACE_OP(operator+=) + PYBIND11_INPLACE_OP(operator-=) + PYBIND11_INPLACE_OP(operator*=) + PYBIND11_INPLACE_OP(operator/=) + PYBIND11_INPLACE_OP(operator|=) + PYBIND11_INPLACE_OP(operator&=) + PYBIND11_INPLACE_OP(operator^=) + PYBIND11_INPLACE_OP(operator<<=) + PYBIND11_INPLACE_OP(operator>>=) +#undef PYBIND11_INPLACE_OP + // Calling cast() on an object lvalue just copies (via handle::cast) template T cast() const &; @@ -413,7 +468,7 @@ PYBIND11_NAMESPACE_BEGIN(detail) // Equivalent to obj.__class__.__name__ (or obj.__name__ if obj is a class). inline const char *obj_class_name(PyObject *obj) { - if (Py_TYPE(obj) == &PyType_Type) { + if (PyType_Check(obj)) { return reinterpret_cast(obj)->tp_name; } return Py_TYPE(obj)->tp_name; @@ -421,13 +476,24 @@ inline const char *obj_class_name(PyObject *obj) { std::string error_string(); +// The code in this struct is very unusual, to minimize the chances of +// masking bugs (elsewhere) by errors during the error handling (here). +// This is meant to be a lifeline for troubleshooting long-running processes +// that crash under conditions that are virtually impossible to reproduce. +// Low-level implementation alternatives are preferred to higher-level ones +// that might raise cascading exceptions. Last-ditch-kind-of attempts are made +// to report as much of the original error as possible, even if there are +// secondary issues obtaining some of the details. struct error_fetch_and_normalize { - // Immediate normalization is long-established behavior (starting with - // https://github.com/pybind/pybind11/commit/135ba8deafb8bf64a15b24d1513899eb600e2011 - // from Sep 2016) and safest. Normalization could be deferred, but this could mask - // errors elsewhere, the performance gain is very minor in typical situations - // (usually the dominant bottleneck is EH unwinding), and the implementation here - // would be more complex. + // This comment only applies to Python <= 3.11: + // Immediate normalization is long-established behavior (starting with + // https://github.com/pybind/pybind11/commit/135ba8deafb8bf64a15b24d1513899eb600e2011 + // from Sep 2016) and safest. Normalization could be deferred, but this could mask + // errors elsewhere, the performance gain is very minor in typical situations + // (usually the dominant bottleneck is EH unwinding), and the implementation here + // would be more complex. + // Starting with Python 3.12, PyErr_Fetch() normalizes exceptions immediately. + // Any errors during normalization are tracked under __notes__. explicit error_fetch_and_normalize(const char *called) { PyErr_Fetch(&m_type.ptr(), &m_value.ptr(), &m_trace.ptr()); if (!m_type) { @@ -442,6 +508,14 @@ struct error_fetch_and_normalize { "of the original active exception type."); } m_lazy_error_string = exc_type_name_orig; +#if PY_VERSION_HEX >= 0x030C0000 + // The presence of __notes__ is likely due to exception normalization + // errors, although that is not necessarily true, therefore insert a + // hint only: + if (PyObject_HasAttrString(m_value.ptr(), "__notes__")) { + m_lazy_error_string += "[WITH __notes__]"; + } +#else // PyErr_NormalizeException() may change the exception type if there are cascading // failures. This can potentially be extremely confusing. PyErr_NormalizeException(&m_type.ptr(), &m_value.ptr(), &m_trace.ptr()); @@ -451,11 +525,17 @@ struct error_fetch_and_normalize { "active exception."); } const char *exc_type_name_norm = detail::obj_class_name(m_type.ptr()); - if (exc_type_name_orig == nullptr) { + if (exc_type_name_norm == nullptr) { pybind11_fail("Internal error: " + std::string(called) + " failed to obtain the name " "of the normalized active exception type."); } +# if defined(PYPY_VERSION_NUM) && PYPY_VERSION_NUM < 0x07030a00 + // This behavior runs the risk of masking errors in the error handling, but avoids a + // conflict with PyPy, which relies on the normalization here to change OSError to + // FileNotFoundError (https://github.com/pybind/pybind11/issues/4075). + m_lazy_error_string = exc_type_name_norm; +# else if (exc_type_name_norm != m_lazy_error_string) { std::string msg = std::string(called) + ": MISMATCH of original and normalized " @@ -467,6 +547,8 @@ struct error_fetch_and_normalize { msg += ": " + format_value_and_trace(); pybind11_fail(msg); } +# endif +#endif } error_fetch_and_normalize(const error_fetch_and_normalize &) = delete; @@ -477,12 +559,64 @@ struct error_fetch_and_normalize { std::string message_error_string; if (m_value) { auto value_str = reinterpret_steal(PyObject_Str(m_value.ptr())); + constexpr const char *message_unavailable_exc + = ""; if (!value_str) { message_error_string = detail::error_string(); - result = ""; + result = message_unavailable_exc; } else { - result = value_str.cast(); + // Not using `value_str.cast()`, to not potentially throw a secondary + // error_already_set that will then result in process termination (#4288). + auto value_bytes = reinterpret_steal( + PyUnicode_AsEncodedString(value_str.ptr(), "utf-8", "backslashreplace")); + if (!value_bytes) { + message_error_string = detail::error_string(); + result = message_unavailable_exc; + } else { + char *buffer = nullptr; + Py_ssize_t length = 0; + if (PyBytes_AsStringAndSize(value_bytes.ptr(), &buffer, &length) == -1) { + message_error_string = detail::error_string(); + result = message_unavailable_exc; + } else { + result = std::string(buffer, static_cast(length)); + } + } } +#if PY_VERSION_HEX >= 0x030B0000 + auto notes + = reinterpret_steal(PyObject_GetAttrString(m_value.ptr(), "__notes__")); + if (!notes) { + PyErr_Clear(); // No notes is good news. + } else { + auto len_notes = PyList_Size(notes.ptr()); + if (len_notes < 0) { + result += "\nFAILURE obtaining len(__notes__): " + detail::error_string(); + } else { + result += "\n__notes__ (len=" + std::to_string(len_notes) + "):"; + for (ssize_t i = 0; i < len_notes; i++) { + PyObject *note = PyList_GET_ITEM(notes.ptr(), i); + auto note_bytes = reinterpret_steal( + PyUnicode_AsEncodedString(note, "utf-8", "backslashreplace")); + if (!note_bytes) { + result += "\nFAILURE obtaining __notes__[" + std::to_string(i) + + "]: " + detail::error_string(); + } else { + char *buffer = nullptr; + Py_ssize_t length = 0; + if (PyBytes_AsStringAndSize(note_bytes.ptr(), &buffer, &length) + == -1) { + result += "\nFAILURE formatting __notes__[" + std::to_string(i) + + "]: " + detail::error_string(); + } else { + result += '\n'; + result += std::string(buffer, static_cast(length)); + } + } + } + } + } +#endif } else { result = ""; } @@ -581,12 +715,6 @@ inline std::string error_string() { PYBIND11_NAMESPACE_END(detail) -#if defined(_MSC_VER) -# pragma warning(push) -# pragma warning(disable : 4275 4251) -// warning C4275: An exported class was derived from a class that wasn't exported. -// Can be ignored when derived from a STL class. -#endif /// Fetch and hold an error which was already set in Python. An instance of this is typically /// thrown to propagate python-side errors back through C++ which can either be caught manually or /// else falls back to the function dispatcher (which then raises the captured error back to @@ -646,9 +774,6 @@ private: /// crashes (undefined behavior) if the Python interpreter is finalizing. static void m_fetched_error_deleter(detail::error_fetch_and_normalize *raw_ptr); }; -#if defined(_MSC_VER) -# pragma warning(pop) -#endif /// Replaces the current Python error indicator with the chosen error, performing a /// 'raise from' to indicate that the chosen error was caused by the original error. @@ -851,10 +976,8 @@ object object_or_cast(T &&o); // Match a PyObject*, which we want to convert directly to handle via its converting constructor inline handle object_or_cast(PyObject *ptr) { return ptr; } -#if defined(_MSC_VER) && _MSC_VER < 1920 -# pragma warning(push) -# pragma warning(disable : 4522) // warning C4522: multiple assignment operators specified -#endif +PYBIND11_WARNING_PUSH +PYBIND11_WARNING_DISABLE_MSVC(4522) // warning C4522: multiple assignment operators specified template class accessor : public object_api> { using key_type = typename Policy::key_type; @@ -918,9 +1041,7 @@ private: key_type key; mutable object cache; }; -#if defined(_MSC_VER) && _MSC_VER < 1920 -# pragma warning(pop) -#endif +PYBIND11_WARNING_POP PYBIND11_NAMESPACE_BEGIN(accessor_policies) struct obj_attr { @@ -1266,7 +1387,7 @@ public: #define PYBIND11_OBJECT_CVT_DEFAULT(Name, Parent, CheckFun, ConvertFun) \ PYBIND11_OBJECT_CVT(Name, Parent, CheckFun, ConvertFun) \ - Name() : Parent() {} + Name() = default; #define PYBIND11_OBJECT_CHECK_FAILED(Name, o_ptr) \ ::pybind11::type_error("Object of type '" \ @@ -1289,7 +1410,7 @@ public: #define PYBIND11_OBJECT_DEFAULT(Name, Parent, CheckFun) \ PYBIND11_OBJECT(Name, Parent, CheckFun) \ - Name() : Parent() {} + Name() = default; /// \addtogroup pytypes /// @{ @@ -1358,7 +1479,7 @@ public: private: void advance() { value = reinterpret_steal(PyIter_Next(m_ptr)); - if (PyErr_Occurred()) { + if (value.ptr() == nullptr && PyErr_Occurred()) { throw error_already_set(); } } @@ -1408,6 +1529,9 @@ public: str(const char *c, const SzType &n) : object(PyUnicode_FromStringAndSize(c, ssize_t_cast(n)), stolen_t{}) { if (!m_ptr) { + if (PyErr_Occurred()) { + throw error_already_set(); + } pybind11_fail("Could not allocate string object!"); } } @@ -1417,6 +1541,9 @@ public: // NOLINTNEXTLINE(google-explicit-constructor) str(const char *c = "") : object(PyUnicode_FromString(c), stolen_t{}) { if (!m_ptr) { + if (PyErr_Occurred()) { + throw error_already_set(); + } pybind11_fail("Could not allocate string object!"); } } @@ -1574,6 +1701,9 @@ inline str::str(const bytes &b) { } auto obj = reinterpret_steal(PyUnicode_FromStringAndSize(buffer, length)); if (!obj) { + if (PyErr_Occurred()) { + throw error_already_set(); + } pybind11_fail("Could not allocate string object!"); } m_ptr = obj.release().ptr(); @@ -1651,7 +1781,7 @@ PYBIND11_NAMESPACE_BEGIN(detail) // unsigned type: (A)-1 != (B)-1 when A and B are unsigned types of different sizes). template Unsigned as_unsigned(PyObject *o) { - if (PYBIND11_SILENCE_MSVC_C4127(sizeof(Unsigned) <= sizeof(unsigned long))) { + if (sizeof(Unsigned) <= sizeof(unsigned long)) { unsigned long v = PyLong_AsUnsignedLong(o); return v == (unsigned long) -1 && PyErr_Occurred() ? (Unsigned) -1 : (Unsigned) v; } @@ -1668,7 +1798,7 @@ public: template ::value, int> = 0> // NOLINTNEXTLINE(google-explicit-constructor) int_(T value) { - if (PYBIND11_SILENCE_MSVC_C4127(sizeof(T) <= sizeof(long))) { + if (sizeof(T) <= sizeof(long)) { if (std::is_signed::value) { m_ptr = PyLong_FromLong((long) value); } else { @@ -1785,43 +1915,28 @@ public: explicit capsule(const void *value, const char *name = nullptr, - void (*destructor)(PyObject *) = nullptr) + PyCapsule_Destructor destructor = nullptr) : object(PyCapsule_New(const_cast(value), name, destructor), stolen_t{}) { if (!m_ptr) { throw error_already_set(); } } - PYBIND11_DEPRECATED("Please pass a destructor that takes a void pointer as input") - capsule(const void *value, void (*destruct)(PyObject *)) - : object(PyCapsule_New(const_cast(value), nullptr, destruct), stolen_t{}) { + PYBIND11_DEPRECATED("Please use the ctor with value, name, destructor args") + capsule(const void *value, PyCapsule_Destructor destructor) + : object(PyCapsule_New(const_cast(value), nullptr, destructor), stolen_t{}) { if (!m_ptr) { throw error_already_set(); } } + /// Capsule name is nullptr. capsule(const void *value, void (*destructor)(void *)) { - m_ptr = PyCapsule_New(const_cast(value), nullptr, [](PyObject *o) { - // guard if destructor called while err indicator is set - error_scope error_guard; - auto destructor = reinterpret_cast(PyCapsule_GetContext(o)); - if (destructor == nullptr) { - if (PyErr_Occurred()) { - throw error_already_set(); - } - pybind11_fail("Unable to get capsule context"); - } - const char *name = get_name_in_error_scope(o); - void *ptr = PyCapsule_GetPointer(o, name); - if (ptr == nullptr) { - throw error_already_set(); - } - destructor(ptr); - }); + initialize_with_void_ptr_destructor(value, nullptr, destructor); + } - if (!m_ptr || PyCapsule_SetContext(m_ptr, (void *) destructor) != 0) { - throw error_already_set(); - } + capsule(const void *value, const char *name, void (*destructor)(void *)) { + initialize_with_void_ptr_destructor(value, name, destructor); } explicit capsule(void (*destructor)()) { @@ -1889,6 +2004,32 @@ private: return name; } + + void initialize_with_void_ptr_destructor(const void *value, + const char *name, + void (*destructor)(void *)) { + m_ptr = PyCapsule_New(const_cast(value), name, [](PyObject *o) { + // guard if destructor called while err indicator is set + error_scope error_guard; + auto destructor = reinterpret_cast(PyCapsule_GetContext(o)); + if (destructor == nullptr && PyErr_Occurred()) { + throw error_already_set(); + } + const char *name = get_name_in_error_scope(o); + void *ptr = PyCapsule_GetPointer(o, name); + if (ptr == nullptr) { + throw error_already_set(); + } + + if (destructor != nullptr) { + destructor(ptr); + } + }); + + if (!m_ptr || PyCapsule_SetContext(m_ptr, reinterpret_cast(destructor)) != 0) { + throw error_already_set(); + } + } }; class tuple : public object { @@ -1943,7 +2084,11 @@ public: void clear() /* py-non-const */ { PyDict_Clear(ptr()); } template bool contains(T &&key) const { - return PyDict_Contains(m_ptr, detail::object_or_cast(std::forward(key)).ptr()) == 1; + auto result = PyDict_Contains(m_ptr, detail::object_or_cast(std::forward(key)).ptr()); + if (result == -1) { + throw error_already_set(); + } + return result == 1; } private: @@ -1998,14 +2143,20 @@ public: detail::list_iterator end() const { return {*this, PyList_GET_SIZE(m_ptr)}; } template void append(T &&val) /* py-non-const */ { - PyList_Append(m_ptr, detail::object_or_cast(std::forward(val)).ptr()); + if (PyList_Append(m_ptr, detail::object_or_cast(std::forward(val)).ptr()) != 0) { + throw error_already_set(); + } } template ::value, int> = 0> void insert(const IdxType &index, ValType &&val) /* py-non-const */ { - PyList_Insert( - m_ptr, ssize_t_cast(index), detail::object_or_cast(std::forward(val)).ptr()); + if (PyList_Insert(m_ptr, + ssize_t_cast(index), + detail::object_or_cast(std::forward(val)).ptr()) + != 0) { + throw error_already_set(); + } } }; @@ -2023,7 +2174,11 @@ public: bool empty() const { return size() == 0; } template bool contains(T &&val) const { - return PySet_Contains(m_ptr, detail::object_or_cast(std::forward(val)).ptr()) == 1; + auto result = PySet_Contains(m_ptr, detail::object_or_cast(std::forward(val)).ptr()); + if (result == -1) { + throw error_already_set(); + } + return result == 1; } }; @@ -2364,29 +2519,39 @@ bool object_api::rich_compare(object_api const &other, int value) const { return result; \ } +#define PYBIND11_MATH_OPERATOR_BINARY_INPLACE(iop, fn) \ + template \ + object object_api::iop(object_api const &other) { \ + object result = reinterpret_steal(fn(derived().ptr(), other.derived().ptr())); \ + if (!result.ptr()) \ + throw error_already_set(); \ + return result; \ + } + PYBIND11_MATH_OPERATOR_UNARY(operator~, PyNumber_Invert) PYBIND11_MATH_OPERATOR_UNARY(operator-, PyNumber_Negative) PYBIND11_MATH_OPERATOR_BINARY(operator+, PyNumber_Add) -PYBIND11_MATH_OPERATOR_BINARY(operator+=, PyNumber_InPlaceAdd) +PYBIND11_MATH_OPERATOR_BINARY_INPLACE(operator+=, PyNumber_InPlaceAdd) PYBIND11_MATH_OPERATOR_BINARY(operator-, PyNumber_Subtract) -PYBIND11_MATH_OPERATOR_BINARY(operator-=, PyNumber_InPlaceSubtract) +PYBIND11_MATH_OPERATOR_BINARY_INPLACE(operator-=, PyNumber_InPlaceSubtract) PYBIND11_MATH_OPERATOR_BINARY(operator*, PyNumber_Multiply) -PYBIND11_MATH_OPERATOR_BINARY(operator*=, PyNumber_InPlaceMultiply) +PYBIND11_MATH_OPERATOR_BINARY_INPLACE(operator*=, PyNumber_InPlaceMultiply) PYBIND11_MATH_OPERATOR_BINARY(operator/, PyNumber_TrueDivide) -PYBIND11_MATH_OPERATOR_BINARY(operator/=, PyNumber_InPlaceTrueDivide) +PYBIND11_MATH_OPERATOR_BINARY_INPLACE(operator/=, PyNumber_InPlaceTrueDivide) PYBIND11_MATH_OPERATOR_BINARY(operator|, PyNumber_Or) -PYBIND11_MATH_OPERATOR_BINARY(operator|=, PyNumber_InPlaceOr) +PYBIND11_MATH_OPERATOR_BINARY_INPLACE(operator|=, PyNumber_InPlaceOr) PYBIND11_MATH_OPERATOR_BINARY(operator&, PyNumber_And) -PYBIND11_MATH_OPERATOR_BINARY(operator&=, PyNumber_InPlaceAnd) +PYBIND11_MATH_OPERATOR_BINARY_INPLACE(operator&=, PyNumber_InPlaceAnd) PYBIND11_MATH_OPERATOR_BINARY(operator^, PyNumber_Xor) -PYBIND11_MATH_OPERATOR_BINARY(operator^=, PyNumber_InPlaceXor) +PYBIND11_MATH_OPERATOR_BINARY_INPLACE(operator^=, PyNumber_InPlaceXor) PYBIND11_MATH_OPERATOR_BINARY(operator<<, PyNumber_Lshift) -PYBIND11_MATH_OPERATOR_BINARY(operator<<=, PyNumber_InPlaceLshift) +PYBIND11_MATH_OPERATOR_BINARY_INPLACE(operator<<=, PyNumber_InPlaceLshift) PYBIND11_MATH_OPERATOR_BINARY(operator>>, PyNumber_Rshift) -PYBIND11_MATH_OPERATOR_BINARY(operator>>=, PyNumber_InPlaceRshift) +PYBIND11_MATH_OPERATOR_BINARY_INPLACE(operator>>=, PyNumber_InPlaceRshift) #undef PYBIND11_MATH_OPERATOR_UNARY #undef PYBIND11_MATH_OPERATOR_BINARY +#undef PYBIND11_MATH_OPERATOR_BINARY_INPLACE PYBIND11_NAMESPACE_END(detail) PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE) diff --git a/pybind11/include/pybind11/stl.h b/pybind11/include/pybind11/stl.h index ab30ecac0..f39f44f7c 100644 --- a/pybind11/include/pybind11/stl.h +++ b/pybind11/include/pybind11/stl.h @@ -45,21 +45,35 @@ using forwarded_type = conditional_t::value, /// Forwards a value U as rvalue or lvalue according to whether T is rvalue or lvalue; typically /// used for forwarding a container's elements. template -forwarded_type forward_like(U &&u) { +constexpr forwarded_type forward_like(U &&u) { return std::forward>(std::forward(u)); } +// Checks if a container has a STL style reserve method. +// This will only return true for a `reserve()` with a `void` return. +template +using has_reserve_method = std::is_same().reserve(0)), void>; + template struct set_caster { using type = Type; using key_conv = make_caster; +private: + template ::value, int> = 0> + void reserve_maybe(const anyset &s, Type *) { + value.reserve(s.size()); + } + void reserve_maybe(const anyset &, void *) {} + +public: bool load(handle src, bool convert) { if (!isinstance(src)) { return false; } auto s = reinterpret_borrow(src); value.clear(); + reserve_maybe(s, &value); for (auto entry : s) { key_conv conv; if (!conv.load(entry, convert)) { @@ -78,7 +92,7 @@ struct set_caster { pybind11::set s; for (auto &&value : src) { auto value_ = reinterpret_steal( - key_conv::cast(forward_like(value), policy, parent)); + key_conv::cast(detail::forward_like(value), policy, parent)); if (!value_ || !s.add(std::move(value_))) { return handle(); } @@ -94,12 +108,21 @@ struct map_caster { using key_conv = make_caster; using value_conv = make_caster; +private: + template ::value, int> = 0> + void reserve_maybe(const dict &d, Type *) { + value.reserve(d.size()); + } + void reserve_maybe(const dict &, void *) {} + +public: bool load(handle src, bool convert) { if (!isinstance(src)) { return false; } auto d = reinterpret_borrow(src); value.clear(); + reserve_maybe(d, &value); for (auto it : d) { key_conv kconv; value_conv vconv; @@ -122,9 +145,9 @@ struct map_caster { } for (auto &&kv : src) { auto key = reinterpret_steal( - key_conv::cast(forward_like(kv.first), policy_key, parent)); + key_conv::cast(detail::forward_like(kv.first), policy_key, parent)); auto value = reinterpret_steal( - value_conv::cast(forward_like(kv.second), policy_value, parent)); + value_conv::cast(detail::forward_like(kv.second), policy_value, parent)); if (!key || !value) { return handle(); } @@ -160,9 +183,7 @@ struct list_caster { } private: - template < - typename T = Type, - enable_if_t().reserve(0)), void>::value, int> = 0> + template ::value, int> = 0> void reserve_maybe(const sequence &s, Type *) { value.reserve(s.size()); } @@ -178,7 +199,7 @@ public: ssize_t index = 0; for (auto &&value : src) { auto value_ = reinterpret_steal( - value_conv::cast(forward_like(value), policy, parent)); + value_conv::cast(detail::forward_like(value), policy, parent)); if (!value_) { return handle(); } @@ -242,7 +263,7 @@ public: ssize_t index = 0; for (auto &&value : src) { auto value_ = reinterpret_steal( - value_conv::cast(forward_like(value), policy, parent)); + value_conv::cast(detail::forward_like(value), policy, parent)); if (!value_) { return handle(); } @@ -252,11 +273,11 @@ public: } PYBIND11_TYPE_CASTER(ArrayType, - const_name("List[") + value_conv::name + const_name(const_name(""), const_name("Annotated[")) + + const_name("List[") + value_conv::name + const_name("]") + const_name(const_name(""), - const_name("[") + const_name() - + const_name("]")) - + const_name("]")); + const_name(", FixedSize(") + + const_name() + const_name(")]"))); }; template @@ -290,11 +311,12 @@ struct optional_caster { template static handle cast(T &&src, return_value_policy policy, handle parent) { if (!src) { - return none().inc_ref(); + return none().release(); } if (!std::is_lvalue_reference::value) { policy = return_value_policy_override::policy(policy); } + // NOLINTNEXTLINE(bugprone-unchecked-optional-access) return value_conv::cast(*std::forward(src), policy, parent); } diff --git a/pybind11/include/pybind11/stl_bind.h b/pybind11/include/pybind11/stl_bind.h index 22a29b476..49f1b7782 100644 --- a/pybind11/include/pybind11/stl_bind.h +++ b/pybind11/include/pybind11/stl_bind.h @@ -10,10 +10,13 @@ #pragma once #include "detail/common.h" +#include "detail/type_caster_base.h" +#include "cast.h" #include "operators.h" #include #include +#include PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE) PYBIND11_NAMESPACE_BEGIN(detail) @@ -58,9 +61,11 @@ struct is_comparable< /* For a vector/map data structure, recursively check the value type (which is std::pair for maps) */ template -struct is_comparable::is_vector>> { - static constexpr const bool value = is_comparable::value; -}; +struct is_comparable::is_vector>> + : is_comparable::type_to_check_recursively> {}; + +template <> +struct is_comparable : std::true_type {}; /* For pairs, recursively check the two data types */ template @@ -352,13 +357,17 @@ void vector_accessor(enable_if_t::value, Class_> &cl) using DiffType = typename Vector::difference_type; using ItType = typename Vector::iterator; cl.def("__getitem__", [](const Vector &v, DiffType i) -> T { - if (i < 0 && (i += v.size()) < 0) { + if (i < 0) { + i += v.size(); + if (i < 0) { + throw index_error(); + } + } + auto i_st = static_cast(i); + if (i_st >= v.size()) { throw index_error(); } - if ((SizeType) i >= v.size()) { - throw index_error(); - } - return v[(SizeType) i]; + return v[i_st]; }); cl.def( @@ -636,18 +645,52 @@ auto map_if_insertion_operator(Class_ &cl, std::string const &name) "Return the canonical string representation of this map."); } -template +template struct keys_view { - Map ↦ + virtual size_t len() = 0; + virtual iterator iter() = 0; + virtual bool contains(const KeyType &k) = 0; + virtual bool contains(const object &k) = 0; + virtual ~keys_view() = default; }; -template +template struct values_view { + virtual size_t len() = 0; + virtual iterator iter() = 0; + virtual ~values_view() = default; +}; + +template +struct items_view { + virtual size_t len() = 0; + virtual iterator iter() = 0; + virtual ~items_view() = default; +}; + +template +struct KeysViewImpl : public KeysView { + explicit KeysViewImpl(Map &map) : map(map) {} + size_t len() override { return map.size(); } + iterator iter() override { return make_key_iterator(map.begin(), map.end()); } + bool contains(const typename Map::key_type &k) override { return map.find(k) != map.end(); } + bool contains(const object &) override { return false; } Map ↦ }; -template -struct items_view { +template +struct ValuesViewImpl : public ValuesView { + explicit ValuesViewImpl(Map &map) : map(map) {} + size_t len() override { return map.size(); } + iterator iter() override { return make_value_iterator(map.begin(), map.end()); } + Map ↦ +}; + +template +struct ItemsViewImpl : public ItemsView { + explicit ItemsViewImpl(Map &map) : map(map) {} + size_t len() override { return map.size(); } + iterator iter() override { return make_iterator(map.begin(), map.end()); } Map ↦ }; @@ -657,9 +700,11 @@ template , typename... class_ bind_map(handle scope, const std::string &name, Args &&...args) { using KeyType = typename Map::key_type; using MappedType = typename Map::mapped_type; - using KeysView = detail::keys_view; - using ValuesView = detail::values_view; - using ItemsView = detail::items_view; + using StrippedKeyType = detail::remove_cvref_t; + using StrippedMappedType = detail::remove_cvref_t; + using KeysView = detail::keys_view; + using ValuesView = detail::values_view; + using ItemsView = detail::items_view; using Class_ = class_; // If either type is a non-module-local bound type then make the map binding non-local as well; @@ -673,12 +718,57 @@ class_ bind_map(handle scope, const std::string &name, Args && } Class_ cl(scope, name.c_str(), pybind11::module_local(local), std::forward(args)...); - class_ keys_view( - scope, ("KeysView[" + name + "]").c_str(), pybind11::module_local(local)); - class_ values_view( - scope, ("ValuesView[" + name + "]").c_str(), pybind11::module_local(local)); - class_ items_view( - scope, ("ItemsView[" + name + "]").c_str(), pybind11::module_local(local)); + static constexpr auto key_type_descr = detail::make_caster::name; + static constexpr auto mapped_type_descr = detail::make_caster::name; + std::string key_type_name(key_type_descr.text), mapped_type_name(mapped_type_descr.text); + + // If key type isn't properly wrapped, fall back to C++ names + if (key_type_name == "%") { + key_type_name = detail::type_info_description(typeid(KeyType)); + } + // Similarly for value type: + if (mapped_type_name == "%") { + mapped_type_name = detail::type_info_description(typeid(MappedType)); + } + + // Wrap KeysView[KeyType] if it wasn't already wrapped + if (!detail::get_type_info(typeid(KeysView))) { + class_ keys_view( + scope, ("KeysView[" + key_type_name + "]").c_str(), pybind11::module_local(local)); + keys_view.def("__len__", &KeysView::len); + keys_view.def("__iter__", + &KeysView::iter, + keep_alive<0, 1>() /* Essential: keep view alive while iterator exists */ + ); + keys_view.def("__contains__", + static_cast(&KeysView::contains)); + // Fallback for when the object is not of the key type + keys_view.def("__contains__", + static_cast(&KeysView::contains)); + } + // Similarly for ValuesView: + if (!detail::get_type_info(typeid(ValuesView))) { + class_ values_view(scope, + ("ValuesView[" + mapped_type_name + "]").c_str(), + pybind11::module_local(local)); + values_view.def("__len__", &ValuesView::len); + values_view.def("__iter__", + &ValuesView::iter, + keep_alive<0, 1>() /* Essential: keep view alive while iterator exists */ + ); + } + // Similarly for ItemsView: + if (!detail::get_type_info(typeid(ItemsView))) { + class_ items_view( + scope, + ("ItemsView[" + key_type_name + ", ").append(mapped_type_name + "]").c_str(), + pybind11::module_local(local)); + items_view.def("__len__", &ItemsView::len); + items_view.def("__iter__", + &ItemsView::iter, + keep_alive<0, 1>() /* Essential: keep view alive while iterator exists */ + ); + } cl.def(init<>()); @@ -698,19 +788,25 @@ class_ bind_map(handle scope, const std::string &name, Args && cl.def( "keys", - [](Map &m) { return KeysView{m}; }, + [](Map &m) { + return std::unique_ptr(new detail::KeysViewImpl(m)); + }, keep_alive<0, 1>() /* Essential: keep map alive while view exists */ ); cl.def( "values", - [](Map &m) { return ValuesView{m}; }, + [](Map &m) { + return std::unique_ptr(new detail::ValuesViewImpl(m)); + }, keep_alive<0, 1>() /* Essential: keep map alive while view exists */ ); cl.def( "items", - [](Map &m) { return ItemsView{m}; }, + [](Map &m) { + return std::unique_ptr(new detail::ItemsViewImpl(m)); + }, keep_alive<0, 1>() /* Essential: keep map alive while view exists */ ); @@ -749,36 +845,6 @@ class_ bind_map(handle scope, const std::string &name, Args && cl.def("__len__", &Map::size); - keys_view.def("__len__", [](KeysView &view) { return view.map.size(); }); - keys_view.def( - "__iter__", - [](KeysView &view) { return make_key_iterator(view.map.begin(), view.map.end()); }, - keep_alive<0, 1>() /* Essential: keep view alive while iterator exists */ - ); - keys_view.def("__contains__", [](KeysView &view, const KeyType &k) -> bool { - auto it = view.map.find(k); - if (it == view.map.end()) { - return false; - } - return true; - }); - // Fallback for when the object is not of the key type - keys_view.def("__contains__", [](KeysView &, const object &) -> bool { return false; }); - - values_view.def("__len__", [](ValuesView &view) { return view.map.size(); }); - values_view.def( - "__iter__", - [](ValuesView &view) { return make_value_iterator(view.map.begin(), view.map.end()); }, - keep_alive<0, 1>() /* Essential: keep view alive while iterator exists */ - ); - - items_view.def("__len__", [](ItemsView &view) { return view.map.size(); }); - items_view.def( - "__iter__", - [](ItemsView &view) { return make_iterator(view.map.begin(), view.map.end()); }, - keep_alive<0, 1>() /* Essential: keep view alive while iterator exists */ - ); - return cl; } diff --git a/pybind11/include/pybind11/type_caster_pyobject_ptr.h b/pybind11/include/pybind11/type_caster_pyobject_ptr.h new file mode 100644 index 000000000..aa914f9e1 --- /dev/null +++ b/pybind11/include/pybind11/type_caster_pyobject_ptr.h @@ -0,0 +1,61 @@ +// Copyright (c) 2023 The pybind Community. + +#pragma once + +#include "detail/common.h" +#include "detail/descr.h" +#include "cast.h" +#include "pytypes.h" + +PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE) +PYBIND11_NAMESPACE_BEGIN(detail) + +template <> +class type_caster { +public: + static constexpr auto name = const_name("object"); // See discussion under PR #4601. + + // This overload is purely to guard against accidents. + template ::value, int> = 0> + static handle cast(T &&, return_value_policy, handle /*parent*/) { + static_assert(is_same_ignoring_cvref::value, + "Invalid C++ type T for to-Python conversion (type_caster)."); + return nullptr; // Unreachable. + } + + static handle cast(PyObject *src, return_value_policy policy, handle /*parent*/) { + if (src == nullptr) { + throw error_already_set(); + } + if (PyErr_Occurred()) { + raise_from(PyExc_SystemError, "src != nullptr but PyErr_Occurred()"); + throw error_already_set(); + } + if (policy == return_value_policy::take_ownership) { + return src; + } + if (policy == return_value_policy::reference + || policy == return_value_policy::automatic_reference) { + return handle(src).inc_ref(); + } + pybind11_fail("type_caster::cast(): unsupported return_value_policy: " + + std::to_string(static_cast(policy))); + } + + bool load(handle src, bool) { + value = reinterpret_borrow(src); + return true; + } + + template + using cast_op_type = PyObject *; + + explicit operator PyObject *() { return value.ptr(); } + +private: + object value; +}; + +PYBIND11_NAMESPACE_END(detail) +PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE) diff --git a/pybind11/noxfile.py b/pybind11/noxfile.py index fe36d70fe..021ced245 100644 --- a/pybind11/noxfile.py +++ b/pybind11/noxfile.py @@ -5,7 +5,17 @@ import nox nox.needs_version = ">=2022.1.7" nox.options.sessions = ["lint", "tests", "tests_packaging"] -PYTHON_VERISONS = ["3.6", "3.7", "3.8", "3.9", "3.10", "3.11", "pypy3.7", "pypy3.8"] +PYTHON_VERSIONS = [ + "3.6", + "3.7", + "3.8", + "3.9", + "3.10", + "3.11", + "pypy3.7", + "pypy3.8", + "pypy3.9", +] if os.environ.get("CI", None): nox.options.error_on_missing_interpreters = True @@ -17,10 +27,10 @@ def lint(session: nox.Session) -> None: Lint the codebase (except for clang-format/tidy). """ session.install("pre-commit") - session.run("pre-commit", "run", "-a") + session.run("pre-commit", "run", "-a", *session.posargs) -@nox.session(python=PYTHON_VERISONS) +@nox.session(python=PYTHON_VERSIONS) def tests(session: nox.Session) -> None: """ Run the tests (requires a compiler). @@ -48,7 +58,7 @@ def tests_packaging(session: nox.Session) -> None: """ session.install("-r", "tests/requirements.txt", "--prefer-binary") - session.run("pytest", "tests/extra_python_package") + session.run("pytest", "tests/extra_python_package", *session.posargs) @nox.session(reuse_venv=True) diff --git a/pybind11/pybind11/__init__.py b/pybind11/pybind11/__init__.py index a1be96981..7c10b3057 100644 --- a/pybind11/pybind11/__init__.py +++ b/pybind11/pybind11/__init__.py @@ -1,16 +1,17 @@ import sys -if sys.version_info < (3, 6): +if sys.version_info < (3, 6): # noqa: UP036 msg = "pybind11 does not support Python < 3.6. 2.9 was the last release supporting Python 2.7 and 3.5." raise ImportError(msg) from ._version import __version__, version_info -from .commands import get_cmake_dir, get_include +from .commands import get_cmake_dir, get_include, get_pkgconfig_dir __all__ = ( "version_info", "__version__", "get_include", "get_cmake_dir", + "get_pkgconfig_dir", ) diff --git a/pybind11/pybind11/__main__.py b/pybind11/pybind11/__main__.py index 22fc4471e..180665c23 100644 --- a/pybind11/pybind11/__main__.py +++ b/pybind11/pybind11/__main__.py @@ -4,7 +4,8 @@ import argparse import sys import sysconfig -from .commands import get_cmake_dir, get_include +from ._version import __version__ +from .commands import get_cmake_dir, get_include, get_pkgconfig_dir def print_includes() -> None: @@ -24,8 +25,13 @@ def print_includes() -> None: def main() -> None: - parser = argparse.ArgumentParser() + parser.add_argument( + "--version", + action="version", + version=__version__, + help="Print the version and exit.", + ) parser.add_argument( "--includes", action="store_true", @@ -36,6 +42,11 @@ def main() -> None: action="store_true", help="Print the CMake module directory, ideal for setting -Dpybind11_ROOT in CMake.", ) + parser.add_argument( + "--pkgconfigdir", + action="store_true", + help="Print the pkgconfig directory, ideal for setting $PKG_CONFIG_PATH.", + ) args = parser.parse_args() if not sys.argv[1:]: parser.print_help() @@ -43,6 +54,8 @@ def main() -> None: print_includes() if args.cmakedir: print(get_cmake_dir()) + if args.pkgconfigdir: + print(get_pkgconfig_dir()) if __name__ == "__main__": diff --git a/pybind11/pybind11/_version.py b/pybind11/pybind11/_version.py index b78d5963d..9280fa054 100644 --- a/pybind11/pybind11/_version.py +++ b/pybind11/pybind11/_version.py @@ -8,5 +8,5 @@ def _to_int(s: str) -> Union[int, str]: return s -__version__ = "2.10.0" +__version__ = "2.11.1" version_info = tuple(_to_int(s) for s in __version__.split(".")) diff --git a/pybind11/pybind11/commands.py b/pybind11/pybind11/commands.py index a29c8ca61..b11690f46 100644 --- a/pybind11/pybind11/commands.py +++ b/pybind11/pybind11/commands.py @@ -3,7 +3,7 @@ import os DIR = os.path.abspath(os.path.dirname(__file__)) -def get_include(user: bool = False) -> str: # pylint: disable=unused-argument +def get_include(user: bool = False) -> str: # noqa: ARG001 """ Return the path to the pybind11 include directory. The historical "user" argument is unused, and may be removed. @@ -23,3 +23,15 @@ def get_cmake_dir() -> str: msg = "pybind11 not installed, installation required to access the CMake files" raise ImportError(msg) + + +def get_pkgconfig_dir() -> str: + """ + Return the path to the pybind11 pkgconfig directory. + """ + pkgconfig_installed_path = os.path.join(DIR, "share", "pkgconfig") + if os.path.exists(pkgconfig_installed_path): + return pkgconfig_installed_path + + msg = "pybind11 not installed, installation required to access the pkgconfig files" + raise ImportError(msg) diff --git a/pybind11/pybind11/setup_helpers.py b/pybind11/pybind11/setup_helpers.py index 1fd04b915..aeeee9dcf 100644 --- a/pybind11/pybind11/setup_helpers.py +++ b/pybind11/pybind11/setup_helpers.py @@ -66,8 +66,8 @@ try: from setuptools import Extension as _Extension from setuptools.command.build_ext import build_ext as _build_ext except ImportError: - from distutils.command.build_ext import build_ext as _build_ext - from distutils.extension import Extension as _Extension + from distutils.command.build_ext import build_ext as _build_ext # type: ignore[assignment] + from distutils.extension import Extension as _Extension # type: ignore[assignment] import distutils.ccompiler import distutils.errors @@ -84,7 +84,7 @@ STD_TMPL = "/std:c++{}" if WIN else "-std=c++{}" # directory into your path if it sits beside your setup.py. -class Pybind11Extension(_Extension): # type: ignore[misc] +class Pybind11Extension(_Extension): """ Build a C++11+ Extension module with pybind11. This automatically adds the recommended flags when you init the extension and assumes C++ sources - you @@ -118,7 +118,6 @@ class Pybind11Extension(_Extension): # type: ignore[misc] self.extra_link_args[:0] = flags def __init__(self, *args: Any, **kwargs: Any) -> None: - self._cxx_level = 0 cxx_std = kwargs.pop("cxx_std", 0) @@ -145,7 +144,6 @@ class Pybind11Extension(_Extension): # type: ignore[misc] self.cxx_std = cxx_std cflags = [] - ldflags = [] if WIN: cflags += ["/EHsc", "/bigobj"] else: @@ -155,11 +153,7 @@ class Pybind11Extension(_Extension): # type: ignore[misc] c_cpp_flags = shlex.split(env_cflags) + shlex.split(env_cppflags) if not any(opt.startswith("-g") for opt in c_cpp_flags): cflags += ["-g0"] - if MACOS: - cflags += ["-stdlib=libc++"] - ldflags += ["-stdlib=libc++"] self._add_cflags(cflags) - self._add_ldflags(ldflags) @property def cxx_std(self) -> int: @@ -174,9 +168,10 @@ class Pybind11Extension(_Extension): # type: ignore[misc] @cxx_std.setter def cxx_std(self, level: int) -> None: - if self._cxx_level: - warnings.warn("You cannot safely change the cxx_level after setting it!") + warnings.warn( + "You cannot safely change the cxx_level after setting it!", stacklevel=2 + ) # MSVC 2015 Update 3 and later only have 14 (and later 17) modes, so # force a valid flag here. @@ -271,7 +266,7 @@ def auto_cpp_level(compiler: Any) -> Union[str, int]: raise RuntimeError(msg) -class build_ext(_build_ext): # type: ignore[misc] # noqa: N801 +class build_ext(_build_ext): # noqa: N801 """ Customized build_ext that allows an auto-search for the highest supported C++ level for Pybind11Extension. This is only needed for the auto-search @@ -341,7 +336,7 @@ def naive_recompile(obj: str, src: str) -> bool: return os.stat(obj).st_mtime < os.stat(src).st_mtime -def no_recompile(obg: str, src: str) -> bool: # pylint: disable=unused-argument +def no_recompile(obg: str, src: str) -> bool: # noqa: ARG001 """ This is the safest but slowest choice (and is the default) - will always recompile sources. @@ -439,7 +434,6 @@ class ParallelCompile: extra_postargs: Optional[List[str]] = None, depends: Optional[List[str]] = None, ) -> Any: - # These lines are directly from distutils.ccompiler.CCompiler macros, objects, extra_postargs, pp_opts, build = compiler._setup_compile( # type: ignore[attr-defined] output_dir, macros, include_dirs, sources, depends, extra_postargs diff --git a/pybind11/pyproject.toml b/pybind11/pyproject.toml index 3ba1b4b22..59c15ea63 100644 --- a/pybind11/pyproject.toml +++ b/pybind11/pyproject.toml @@ -2,6 +2,7 @@ requires = ["setuptools>=42", "cmake>=3.18", "ninja"] build-backend = "setuptools.build_meta" + [tool.check-manifest] ignore = [ "tests/**", @@ -15,11 +16,6 @@ ignore = [ "noxfile.py", ] -[tool.isort] -# Needs the compiled .so modules and env.py from tests -known_first_party = "env,pybind11_cross_module_tests,pybind11_tests," -# For black compatibility -profile = "black" [tool.mypy] files = ["pybind11"] @@ -30,7 +26,7 @@ enable_error_code = ["ignore-without-code", "redundant-expr", "truthy-bool"] warn_unreachable = true [[tool.mypy.overrides]] -module = ["ghapi.*", "setuptools.*"] +module = ["ghapi.*"] ignore_missing_imports = true @@ -58,4 +54,45 @@ messages_control.disable = [ "invalid-name", "protected-access", "missing-module-docstring", + "unused-argument", # covered by Ruff ARG ] + + +[tool.ruff] +select = [ + "E", "F", "W", # flake8 + "B", # flake8-bugbear + "I", # isort + "N", # pep8-naming + "ARG", # flake8-unused-arguments + "C4", # flake8-comprehensions + "EM", # flake8-errmsg + "ICN", # flake8-import-conventions + "ISC", # flake8-implicit-str-concat + "PGH", # pygrep-hooks + "PIE", # flake8-pie + "PL", # pylint + "PT", # flake8-pytest-style + "RET", # flake8-return + "RUF100", # Ruff-specific + "SIM", # flake8-simplify + "UP", # pyupgrade + "YTT", # flake8-2020 +] +ignore = [ + "PLR", # Design related pylint + "E501", # Line too long (Black is enough) + "PT011", # Too broad with raises in pytest + "PT004", # Fixture that doesn't return needs underscore (no, it is fine) + "SIM118", # iter(x) is not always the same as iter(x.keys()) +] +target-version = "py37" +src = ["src"] +unfixable = ["T20"] +exclude = [] +line-length = 120 +isort.known-first-party = ["env", "pybind11_cross_module_tests", "pybind11_tests"] + +[tool.ruff.per-file-ignores] +"tests/**" = ["EM", "N"] +"tests/test_call_policies.py" = ["PLC1901"] diff --git a/pybind11/setup.cfg b/pybind11/setup.cfg index 36fbfed4f..92e6c953a 100644 --- a/pybind11/setup.cfg +++ b/pybind11/setup.cfg @@ -20,6 +20,7 @@ classifiers = Programming Language :: Python :: 3.9 Programming Language :: Python :: 3.10 Programming Language :: Python :: 3.11 + Programming Language :: Python :: 3.12 License :: OSI Approved :: BSD License Programming Language :: Python :: Implementation :: PyPy Programming Language :: Python :: Implementation :: CPython @@ -40,11 +41,3 @@ project_urls = [options] python_requires = >=3.6 zip_safe = False - - -[flake8] -max-line-length = 120 -show_source = True -exclude = .git, __pycache__, build, dist, docs, tools, venv -extend-ignore = E203, E722, B950 -extend-select = B9 diff --git a/pybind11/setup.py b/pybind11/setup.py index a149755bf..9fea7d35c 100644 --- a/pybind11/setup.py +++ b/pybind11/setup.py @@ -96,7 +96,7 @@ def get_and_replace( # Use our input files instead when making the SDist (and anything that depends # on it, like a wheel) -class SDist(setuptools.command.sdist.sdist): # type: ignore[misc] +class SDist(setuptools.command.sdist.sdist): def make_release_tree(self, base_dir: str, files: List[str]) -> None: super().make_release_tree(base_dir, files) @@ -127,6 +127,7 @@ with remove_output("pybind11/include", "pybind11/share"): "-DCMAKE_INSTALL_PREFIX=pybind11", "-DBUILD_TESTING=OFF", "-DPYBIND11_NOPYTHON=ON", + "-Dprefix_for_pc_file=${pcfiledir}/../../", ] if "CMAKE_ARGS" in os.environ: fcommand = [ diff --git a/pybind11/tests/CMakeLists.txt b/pybind11/tests/CMakeLists.txt index 7296cd1b8..80ee9c1f1 100644 --- a/pybind11/tests/CMakeLists.txt +++ b/pybind11/tests/CMakeLists.txt @@ -5,20 +5,17 @@ # All rights reserved. Use of this source code is governed by a # BSD-style license that can be found in the LICENSE file. -cmake_minimum_required(VERSION 3.4) +cmake_minimum_required(VERSION 3.5) -# The `cmake_minimum_required(VERSION 3.4...3.18)` syntax does not work with +# The `cmake_minimum_required(VERSION 3.5...3.26)` syntax does not work with # some versions of VS that have a patched CMake 3.11. This forces us to emulate # the behavior using the following workaround: -if(${CMAKE_VERSION} VERSION_LESS 3.21) +if(${CMAKE_VERSION} VERSION_LESS 3.26) cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}) else() - cmake_policy(VERSION 3.21) + cmake_policy(VERSION 3.26) endif() -# Only needed for CMake < 3.5 support -include(CMakeParseArguments) - # Filter out items; print an optional message if any items filtered. This ignores extensions. # # Usage: @@ -128,7 +125,8 @@ set(PYBIND11_TEST_FILES test_custom_type_casters test_custom_type_setup test_docstring_options - test_eigen + test_eigen_matrix + test_eigen_tensor test_enum test_eval test_exceptions @@ -153,7 +151,11 @@ set(PYBIND11_TEST_FILES test_stl_binders test_tagbased_polymorphic test_thread + test_type_caster_pyobject_ptr test_union + test_unnamed_namespace_a + test_unnamed_namespace_b + test_vector_unique_ptr_member test_virtual_functions) # Invoking cmake with something like: @@ -168,7 +170,7 @@ if(PYBIND11_TEST_OVERRIDE) # This allows the override to be done with extensions, preserving backwards compatibility. foreach(test_name ${TEST_FILES_NO_EXT}) if(NOT ${test_name} IN_LIST TEST_OVERRIDE_NO_EXT - )# If not in the whitelist, add to be filtered out. + )# If not in the allowlist, add to be filtered out. list(APPEND PYBIND11_TEST_FILTER ${test_name}) endif() endforeach() @@ -233,7 +235,10 @@ list(GET PYBIND11_EIGEN_VERSION_AND_HASH 1 PYBIND11_EIGEN_VERSION_HASH) # Check if Eigen is available; if not, remove from PYBIND11_TEST_FILES (but # keep it in PYBIND11_PYTEST_FILES, so that we get the "eigen is not installed" # skip message). -list(FIND PYBIND11_TEST_FILES test_eigen.cpp PYBIND11_TEST_FILES_EIGEN_I) +list(FIND PYBIND11_TEST_FILES test_eigen_matrix.cpp PYBIND11_TEST_FILES_EIGEN_I) +if(PYBIND11_TEST_FILES_EIGEN_I EQUAL -1) + list(FIND PYBIND11_TEST_FILES test_eigen_tensor.cpp PYBIND11_TEST_FILES_EIGEN_I) +endif() if(PYBIND11_TEST_FILES_EIGEN_I GREATER -1) # Try loading via newer Eigen's Eigen3Config first (bypassing tools/FindEigen3.cmake). # Eigen 3.3.1+ exports a cmake 3.0+ target for handling dependency requirements, but also @@ -288,13 +293,34 @@ if(PYBIND11_TEST_FILES_EIGEN_I GREATER -1) set(EIGEN3_VERSION ${EIGEN3_VERSION_STRING}) endif() message(STATUS "Building tests with Eigen v${EIGEN3_VERSION}") + + if(NOT (CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 5.0)) + tests_extra_targets("test_eigen_tensor.py" "eigen_tensor_avoid_stl_array") + endif() + else() - list(REMOVE_AT PYBIND11_TEST_FILES ${PYBIND11_TEST_FILES_EIGEN_I}) + list(FIND PYBIND11_TEST_FILES test_eigen_matrix.cpp PYBIND11_TEST_FILES_EIGEN_I) + if(PYBIND11_TEST_FILES_EIGEN_I GREATER -1) + list(REMOVE_AT PYBIND11_TEST_FILES ${PYBIND11_TEST_FILES_EIGEN_I}) + endif() + + list(FIND PYBIND11_TEST_FILES test_eigen_tensor.cpp PYBIND11_TEST_FILES_EIGEN_I) + if(PYBIND11_TEST_FILES_EIGEN_I GREATER -1) + list(REMOVE_AT PYBIND11_TEST_FILES ${PYBIND11_TEST_FILES_EIGEN_I}) + endif() message( STATUS "Building tests WITHOUT Eigen, use -DDOWNLOAD_EIGEN=ON on CMake 3.11+ to download") endif() endif() +# Some code doesn't support gcc 4 +if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 5.0) + list(FIND PYBIND11_TEST_FILES test_eigen_tensor.cpp PYBIND11_TEST_FILES_EIGEN_I) + if(PYBIND11_TEST_FILES_EIGEN_I GREATER -1) + list(REMOVE_AT PYBIND11_TEST_FILES ${PYBIND11_TEST_FILES_EIGEN_I}) + endif() +endif() + # Optional dependency for some tests (boost::variant is only supported with version >= 1.56) find_package(Boost 1.56) diff --git a/pybind11/tests/conftest.py b/pybind11/tests/conftest.py index 02ce263af..ad5b47b4b 100644 --- a/pybind11/tests/conftest.py +++ b/pybind11/tests/conftest.py @@ -7,13 +7,36 @@ Adds docstring and exceptions message sanitizers. import contextlib import difflib import gc +import multiprocessing import re +import sys import textwrap +import traceback import pytest # Early diagnostic for failed imports -import pybind11_tests +try: + import pybind11_tests +except Exception: + # pytest does not show the traceback without this. + traceback.print_exc() + raise + + +@pytest.fixture(scope="session", autouse=True) +def use_multiprocessing_forkserver_on_linux(): + if sys.platform != "linux": + # The default on Windows and macOS is "spawn": If it's not broken, don't fix it. + return + + # Full background: https://github.com/pybind/pybind11/issues/4105#issuecomment-1301004592 + # In a nutshell: fork() after starting threads == flakiness in the form of deadlocks. + # It is actually a well-known pitfall, unfortunately without guard rails. + # "forkserver" is more performant than "spawn" (~9s vs ~13s for tests/test_gil_scoped.py, + # visit the issuecomment link above for details). + multiprocessing.set_start_method("forkserver") + _long_marker = re.compile(r"([0-9])L") _hexadecimal = re.compile(r"0x[0-9a-fA-F]+") @@ -59,9 +82,8 @@ class Output: b = _strip_and_dedent(other).splitlines() if a == b: return True - else: - self.explanation = _make_explanation(a, b) - return False + self.explanation = _make_explanation(a, b) + return False class Unordered(Output): @@ -72,9 +94,8 @@ class Unordered(Output): b = _split_and_sort(other) if a == b: return True - else: - self.explanation = _make_explanation(a, b) - return False + self.explanation = _make_explanation(a, b) + return False class Capture: @@ -95,9 +116,8 @@ class Capture: b = other if a == b: return True - else: - self.explanation = a.explanation - return False + self.explanation = a.explanation + return False def __str__(self): return self.out @@ -114,7 +134,7 @@ class Capture: return Output(self.err) -@pytest.fixture +@pytest.fixture() def capture(capsys): """Extended `capsys` with context manager and custom equality operators""" return Capture(capsys) @@ -135,25 +155,22 @@ class SanitizedString: b = _strip_and_dedent(other) if a == b: return True - else: - self.explanation = _make_explanation(a.splitlines(), b.splitlines()) - return False + self.explanation = _make_explanation(a.splitlines(), b.splitlines()) + return False def _sanitize_general(s): s = s.strip() s = s.replace("pybind11_tests.", "m.") - s = _long_marker.sub(r"\1", s) - return s + return _long_marker.sub(r"\1", s) def _sanitize_docstring(thing): s = thing.__doc__ - s = _sanitize_general(s) - return s + return _sanitize_general(s) -@pytest.fixture +@pytest.fixture() def doc(): """Sanitize docstrings and add custom failure explanation""" return SanitizedString(_sanitize_docstring) @@ -162,30 +179,20 @@ def doc(): def _sanitize_message(thing): s = str(thing) s = _sanitize_general(s) - s = _hexadecimal.sub("0", s) - return s + return _hexadecimal.sub("0", s) -@pytest.fixture +@pytest.fixture() def msg(): """Sanitize messages and add custom failure explanation""" return SanitizedString(_sanitize_message) -# noinspection PyUnusedLocal -def pytest_assertrepr_compare(op, left, right): +def pytest_assertrepr_compare(op, left, right): # noqa: ARG001 """Hook to insert custom failure explanation""" if hasattr(left, "explanation"): return left.explanation - - -@contextlib.contextmanager -def suppress(exception): - """Suppress the desired exception""" - try: - yield - except exception: - pass + return None def gc_collect(): @@ -196,7 +203,7 @@ def gc_collect(): def pytest_configure(): - pytest.suppress = suppress + pytest.suppress = contextlib.suppress pytest.gc_collect = gc_collect @@ -210,4 +217,5 @@ def pytest_report_header(config): f" {pybind11_tests.compiler_info}" f" {pybind11_tests.cpp_std}" f" {pybind11_tests.PYBIND11_INTERNALS_ID}" + f" PYBIND11_SIMPLE_GIL_MANAGEMENT={pybind11_tests.PYBIND11_SIMPLE_GIL_MANAGEMENT}" ) diff --git a/pybind11/tests/constructor_stats.h b/pybind11/tests/constructor_stats.h index a3835c21e..937f6c233 100644 --- a/pybind11/tests/constructor_stats.h +++ b/pybind11/tests/constructor_stats.h @@ -115,7 +115,7 @@ public: #if defined(PYPY_VERSION) PyObject *globals = PyEval_GetGlobals(); PyObject *result = PyRun_String("import gc\n" - "for i in range(2):" + "for i in range(2):\n" " gc.collect()\n", Py_file_input, globals, diff --git a/pybind11/tests/cross_module_gil_utils.cpp b/pybind11/tests/cross_module_gil_utils.cpp index 1436c35d6..7c20849dd 100644 --- a/pybind11/tests/cross_module_gil_utils.cpp +++ b/pybind11/tests/cross_module_gil_utils.cpp @@ -6,9 +6,15 @@ All rights reserved. Use of this source code is governed by a BSD-style license that can be found in the LICENSE file. */ +#if defined(PYBIND11_INTERNALS_VERSION) +# undef PYBIND11_INTERNALS_VERSION +#endif +#define PYBIND11_INTERNALS_VERSION 21814642 // Ensure this module has its own `internals` instance. #include #include +#include +#include // This file mimics a DSO that makes pybind11 calls but does not define a // PYBIND11_MODULE. The purpose is to test that such a DSO can create a @@ -21,8 +27,54 @@ namespace { namespace py = pybind11; + void gil_acquire() { py::gil_scoped_acquire gil; } +std::string gil_multi_acquire_release(unsigned bits) { + if ((bits & 0x1u) != 0u) { + py::gil_scoped_acquire gil; + } + if ((bits & 0x2u) != 0u) { + py::gil_scoped_release gil; + } + if ((bits & 0x4u) != 0u) { + py::gil_scoped_acquire gil; + } + if ((bits & 0x8u) != 0u) { + py::gil_scoped_release gil; + } + return PYBIND11_INTERNALS_ID; +} + +struct CustomAutoGIL { + CustomAutoGIL() : gstate(PyGILState_Ensure()) {} + ~CustomAutoGIL() { PyGILState_Release(gstate); } + + PyGILState_STATE gstate; +}; +struct CustomAutoNoGIL { + CustomAutoNoGIL() : save(PyEval_SaveThread()) {} + ~CustomAutoNoGIL() { PyEval_RestoreThread(save); } + + PyThreadState *save; +}; + +template +void gil_acquire_inner() { + Acquire acquire_outer; + Acquire acquire_inner; + Release release; +} + +template +void gil_acquire_nested() { + Acquire acquire_outer; + Acquire acquire_inner; + Release release; + auto thread = std::thread(&gil_acquire_inner); + thread.join(); +} + constexpr char kModuleName[] = "cross_module_gil_utils"; struct PyModuleDef moduledef = { @@ -30,6 +82,9 @@ struct PyModuleDef moduledef = { } // namespace +#define ADD_FUNCTION(Name, ...) \ + PyModule_AddObject(m, Name, PyLong_FromVoidPtr(reinterpret_cast(&__VA_ARGS__))); + extern "C" PYBIND11_EXPORT PyObject *PyInit_cross_module_gil_utils() { PyObject *m = PyModule_Create(&moduledef); @@ -37,8 +92,16 @@ extern "C" PYBIND11_EXPORT PyObject *PyInit_cross_module_gil_utils() { if (m != nullptr) { static_assert(sizeof(&gil_acquire) == sizeof(void *), "Function pointer must have the same size as void*"); - PyModule_AddObject( - m, "gil_acquire_funcaddr", PyLong_FromVoidPtr(reinterpret_cast(&gil_acquire))); + ADD_FUNCTION("gil_acquire_funcaddr", gil_acquire) + ADD_FUNCTION("gil_multi_acquire_release_funcaddr", gil_multi_acquire_release) + ADD_FUNCTION("gil_acquire_inner_custom_funcaddr", + gil_acquire_inner) + ADD_FUNCTION("gil_acquire_nested_custom_funcaddr", + gil_acquire_nested) + ADD_FUNCTION("gil_acquire_inner_pybind11_funcaddr", + gil_acquire_inner) + ADD_FUNCTION("gil_acquire_nested_pybind11_funcaddr", + gil_acquire_nested) } return m; diff --git a/pybind11/tests/eigen_tensor_avoid_stl_array.cpp b/pybind11/tests/eigen_tensor_avoid_stl_array.cpp new file mode 100644 index 000000000..eacc9e9bd --- /dev/null +++ b/pybind11/tests/eigen_tensor_avoid_stl_array.cpp @@ -0,0 +1,14 @@ +/* + tests/eigen_tensor.cpp -- automatic conversion of Eigen Tensor + + All rights reserved. Use of this source code is governed by a + BSD-style license that can be found in the LICENSE file. +*/ + +#ifndef EIGEN_AVOID_STL_ARRAY +# define EIGEN_AVOID_STL_ARRAY +#endif + +#include "test_eigen_tensor.inl" + +PYBIND11_MODULE(eigen_tensor_avoid_stl_array, m) { eigen_tensor_test::test_module(m); } diff --git a/pybind11/tests/env.py b/pybind11/tests/env.py index 0345df65d..7eea5a3b3 100644 --- a/pybind11/tests/env.py +++ b/pybind11/tests/env.py @@ -24,5 +24,4 @@ def deprecated_call(): pytest_major_minor = (int(pieces[0]), int(pieces[1])) if pytest_major_minor < (3, 9): return pytest.warns((DeprecationWarning, PendingDeprecationWarning)) - else: - return pytest.deprecated_call() + return pytest.deprecated_call() diff --git a/pybind11/tests/extra_python_package/test_files.py b/pybind11/tests/extra_python_package/test_files.py index ba16b5224..57387dd8b 100644 --- a/pybind11/tests/extra_python_package/test_files.py +++ b/pybind11/tests/extra_python_package/test_files.py @@ -12,6 +12,16 @@ import zipfile DIR = os.path.abspath(os.path.dirname(__file__)) MAIN_DIR = os.path.dirname(os.path.dirname(DIR)) +PKGCONFIG = """\ +prefix=${{pcfiledir}}/../../ +includedir=${{prefix}}/include + +Name: pybind11 +Description: Seamless operability between C++11 and Python +Version: {VERSION} +Cflags: -I${{includedir}} +""" + main_headers = { "include/pybind11/attr.h", @@ -33,6 +43,7 @@ main_headers = { "include/pybind11/pytypes.h", "include/pybind11/stl.h", "include/pybind11/stl_bind.h", + "include/pybind11/type_caster_pyobject_ptr.h", } detail_headers = { @@ -45,6 +56,12 @@ detail_headers = { "include/pybind11/detail/typeid.h", } +eigen_headers = { + "include/pybind11/eigen/common.h", + "include/pybind11/eigen/matrix.h", + "include/pybind11/eigen/tensor.h", +} + stl_headers = { "include/pybind11/stl/filesystem.h", } @@ -59,6 +76,10 @@ cmake_files = { "share/cmake/pybind11/pybind11Tools.cmake", } +pkgconfig_files = { + "share/pkgconfig/pybind11.pc", +} + py_files = { "__init__.py", "__main__.py", @@ -68,8 +89,8 @@ py_files = { "setup_helpers.py", } -headers = main_headers | detail_headers | stl_headers -src_files = headers | cmake_files +headers = main_headers | detail_headers | eigen_headers | stl_headers +src_files = headers | cmake_files | pkgconfig_files all_files = src_files | py_files @@ -78,10 +99,12 @@ sdist_files = { "pybind11/include", "pybind11/include/pybind11", "pybind11/include/pybind11/detail", + "pybind11/include/pybind11/eigen", "pybind11/include/pybind11/stl", "pybind11/share", "pybind11/share/cmake", "pybind11/share/cmake/pybind11", + "pybind11/share/pkgconfig", "pyproject.toml", "setup.cfg", "setup.py", @@ -89,6 +112,7 @@ sdist_files = { "MANIFEST.in", "README.rst", "PKG-INFO", + "SECURITY.md", } local_sdist_files = { @@ -101,22 +125,24 @@ local_sdist_files = { } -def test_build_sdist(monkeypatch, tmpdir): +def read_tz_file(tar: tarfile.TarFile, name: str) -> bytes: + start = tar.getnames()[0] + "/" + inner_file = tar.extractfile(tar.getmember(f"{start}{name}")) + assert inner_file + with contextlib.closing(inner_file) as f: + return f.read() + +def normalize_line_endings(value: bytes) -> bytes: + return value.replace(os.linesep.encode("utf-8"), b"\n") + + +def test_build_sdist(monkeypatch, tmpdir): monkeypatch.chdir(MAIN_DIR) - out = subprocess.check_output( - [ - sys.executable, - "-m", - "build", - "--sdist", - "--outdir", - str(tmpdir), - ] + subprocess.run( + [sys.executable, "-m", "build", "--sdist", f"--outdir={tmpdir}"], check=True ) - if hasattr(out, "decode"): - out = out.decode() (sdist,) = tmpdir.visit("*.tar.gz") @@ -125,25 +151,17 @@ def test_build_sdist(monkeypatch, tmpdir): version = start[9:-1] simpler = {n.split("/", 1)[-1] for n in tar.getnames()[1:]} - with contextlib.closing( - tar.extractfile(tar.getmember(start + "setup.py")) - ) as f: - setup_py = f.read() + setup_py = read_tz_file(tar, "setup.py") + pyproject_toml = read_tz_file(tar, "pyproject.toml") + pkgconfig = read_tz_file(tar, "pybind11/share/pkgconfig/pybind11.pc") + cmake_cfg = read_tz_file( + tar, "pybind11/share/cmake/pybind11/pybind11Config.cmake" + ) - with contextlib.closing( - tar.extractfile(tar.getmember(start + "pyproject.toml")) - ) as f: - pyproject_toml = f.read() - - with contextlib.closing( - tar.extractfile( - tar.getmember( - start + "pybind11/share/cmake/pybind11/pybind11Config.cmake" - ) - ) - ) as f: - contents = f.read().decode("utf8") - assert 'set(pybind11_INCLUDE_DIR "${PACKAGE_PREFIX_DIR}/include")' in contents + assert ( + 'set(pybind11_INCLUDE_DIR "${PACKAGE_PREFIX_DIR}/include")' + in cmake_cfg.decode("utf-8") + ) files = {f"pybind11/{n}" for n in all_files} files |= sdist_files @@ -154,9 +172,9 @@ def test_build_sdist(monkeypatch, tmpdir): with open(os.path.join(MAIN_DIR, "tools", "setup_main.py.in"), "rb") as f: contents = ( - string.Template(f.read().decode()) + string.Template(f.read().decode("utf-8")) .substitute(version=version, extra_cmd="") - .encode() + .encode("utf-8") ) assert setup_py == contents @@ -164,25 +182,18 @@ def test_build_sdist(monkeypatch, tmpdir): contents = f.read() assert pyproject_toml == contents + simple_version = ".".join(version.split(".")[:3]) + pkgconfig_expected = PKGCONFIG.format(VERSION=simple_version).encode("utf-8") + assert normalize_line_endings(pkgconfig) == pkgconfig_expected + def test_build_global_dist(monkeypatch, tmpdir): - monkeypatch.chdir(MAIN_DIR) monkeypatch.setenv("PYBIND11_GLOBAL_SDIST", "1") - out = subprocess.check_output( - [ - sys.executable, - "-m", - "build", - "--sdist", - "--outdir", - str(tmpdir), - ] + subprocess.run( + [sys.executable, "-m", "build", "--sdist", "--outdir", str(tmpdir)], check=True ) - if hasattr(out, "decode"): - out = out.decode() - (sdist,) = tmpdir.visit("*.tar.gz") with tarfile.open(str(sdist), "r:gz") as tar: @@ -190,15 +201,17 @@ def test_build_global_dist(monkeypatch, tmpdir): version = start[16:-1] simpler = {n.split("/", 1)[-1] for n in tar.getnames()[1:]} - with contextlib.closing( - tar.extractfile(tar.getmember(start + "setup.py")) - ) as f: - setup_py = f.read() + setup_py = read_tz_file(tar, "setup.py") + pyproject_toml = read_tz_file(tar, "pyproject.toml") + pkgconfig = read_tz_file(tar, "pybind11/share/pkgconfig/pybind11.pc") + cmake_cfg = read_tz_file( + tar, "pybind11/share/cmake/pybind11/pybind11Config.cmake" + ) - with contextlib.closing( - tar.extractfile(tar.getmember(start + "pyproject.toml")) - ) as f: - pyproject_toml = f.read() + assert ( + 'set(pybind11_INCLUDE_DIR "${PACKAGE_PREFIX_DIR}/include")' + in cmake_cfg.decode("utf-8") + ) files = {f"pybind11/{n}" for n in all_files} files |= sdist_files @@ -209,7 +222,7 @@ def test_build_global_dist(monkeypatch, tmpdir): contents = ( string.Template(f.read().decode()) .substitute(version=version, extra_cmd="") - .encode() + .encode("utf-8") ) assert setup_py == contents @@ -217,12 +230,16 @@ def test_build_global_dist(monkeypatch, tmpdir): contents = f.read() assert pyproject_toml == contents + simple_version = ".".join(version.split(".")[:3]) + pkgconfig_expected = PKGCONFIG.format(VERSION=simple_version).encode("utf-8") + assert normalize_line_endings(pkgconfig) == pkgconfig_expected + def tests_build_wheel(monkeypatch, tmpdir): monkeypatch.chdir(MAIN_DIR) - subprocess.check_output( - [sys.executable, "-m", "pip", "wheel", ".", "-w", str(tmpdir)] + subprocess.run( + [sys.executable, "-m", "pip", "wheel", ".", "-w", str(tmpdir)], check=True ) (wheel,) = tmpdir.visit("*.whl") @@ -249,8 +266,8 @@ def tests_build_global_wheel(monkeypatch, tmpdir): monkeypatch.chdir(MAIN_DIR) monkeypatch.setenv("PYBIND11_GLOBAL_SDIST", "1") - subprocess.check_output( - [sys.executable, "-m", "pip", "wheel", ".", "-w", str(tmpdir)] + subprocess.run( + [sys.executable, "-m", "pip", "wheel", ".", "-w", str(tmpdir)], check=True ) (wheel,) = tmpdir.visit("*.whl") diff --git a/pybind11/tests/pybind11_tests.cpp b/pybind11/tests/pybind11_tests.cpp index aa3095594..624034648 100644 --- a/pybind11/tests/pybind11_tests.cpp +++ b/pybind11/tests/pybind11_tests.cpp @@ -89,6 +89,12 @@ PYBIND11_MODULE(pybind11_tests, m) { #endif m.attr("cpp_std") = cpp_std(); m.attr("PYBIND11_INTERNALS_ID") = PYBIND11_INTERNALS_ID; + m.attr("PYBIND11_SIMPLE_GIL_MANAGEMENT") = +#if defined(PYBIND11_SIMPLE_GIL_MANAGEMENT) + true; +#else + false; +#endif bind_ConstructorStats(m); diff --git a/pybind11/tests/requirements.txt b/pybind11/tests/requirements.txt index 04aafa8cf..4ba101119 100644 --- a/pybind11/tests/requirements.txt +++ b/pybind11/tests/requirements.txt @@ -6,4 +6,4 @@ numpy==1.22.2; platform_python_implementation!="PyPy" and python_version>="3.10" pytest==7.0.0 pytest-timeout scipy==1.5.4; platform_python_implementation!="PyPy" and python_version<"3.10" -scipy==1.8.0; platform_python_implementation!="PyPy" and python_version=="3.10" +scipy==1.10.0; platform_python_implementation!="PyPy" and python_version=="3.10" diff --git a/pybind11/tests/test_async.py b/pybind11/tests/test_async.py index b9ff9514d..83a4c5036 100644 --- a/pybind11/tests/test_async.py +++ b/pybind11/tests/test_async.py @@ -4,7 +4,7 @@ asyncio = pytest.importorskip("asyncio") m = pytest.importorskip("pybind11_tests.async_module") -@pytest.fixture +@pytest.fixture() def event_loop(): loop = asyncio.new_event_loop() yield loop @@ -16,7 +16,7 @@ async def get_await_result(x): def test_await(event_loop): - assert 5 == event_loop.run_until_complete(get_await_result(m.SupportsAsync())) + assert event_loop.run_until_complete(get_await_result(m.SupportsAsync())) == 5 def test_await_missing(event_loop): diff --git a/pybind11/tests/test_buffers.cpp b/pybind11/tests/test_buffers.cpp index 6b6e8cba7..b5b8c355b 100644 --- a/pybind11/tests/test_buffers.cpp +++ b/pybind11/tests/test_buffers.cpp @@ -7,12 +7,47 @@ BSD-style license that can be found in the LICENSE file. */ +#include #include #include "constructor_stats.h" #include "pybind11_tests.h" TEST_SUBMODULE(buffers, m) { + m.attr("long_double_and_double_have_same_size") = (sizeof(long double) == sizeof(double)); + + m.def("format_descriptor_format_buffer_info_equiv", + [](const std::string &cpp_name, const py::buffer &buffer) { + // https://google.github.io/styleguide/cppguide.html#Static_and_Global_Variables + static auto *format_table = new std::map; + static auto *equiv_table + = new std::map; + if (format_table->empty()) { +#define PYBIND11_ASSIGN_HELPER(...) \ + (*format_table)[#__VA_ARGS__] = py::format_descriptor<__VA_ARGS__>::format(); \ + (*equiv_table)[#__VA_ARGS__] = &py::buffer_info::item_type_is_equivalent_to<__VA_ARGS__>; + PYBIND11_ASSIGN_HELPER(PyObject *) + PYBIND11_ASSIGN_HELPER(bool) + PYBIND11_ASSIGN_HELPER(std::int8_t) + PYBIND11_ASSIGN_HELPER(std::uint8_t) + PYBIND11_ASSIGN_HELPER(std::int16_t) + PYBIND11_ASSIGN_HELPER(std::uint16_t) + PYBIND11_ASSIGN_HELPER(std::int32_t) + PYBIND11_ASSIGN_HELPER(std::uint32_t) + PYBIND11_ASSIGN_HELPER(std::int64_t) + PYBIND11_ASSIGN_HELPER(std::uint64_t) + PYBIND11_ASSIGN_HELPER(float) + PYBIND11_ASSIGN_HELPER(double) + PYBIND11_ASSIGN_HELPER(long double) + PYBIND11_ASSIGN_HELPER(std::complex) + PYBIND11_ASSIGN_HELPER(std::complex) + PYBIND11_ASSIGN_HELPER(std::complex) +#undef PYBIND11_ASSIGN_HELPER + } + return std::pair( + (*format_table)[cpp_name], (buffer.request().*((*equiv_table)[cpp_name]))()); + }); + // test_from_python / test_to_python: class Matrix { public: diff --git a/pybind11/tests/test_buffers.py b/pybind11/tests/test_buffers.py index 8354b68cd..63d9d869f 100644 --- a/pybind11/tests/test_buffers.py +++ b/pybind11/tests/test_buffers.py @@ -10,6 +10,63 @@ from pybind11_tests import buffers as m np = pytest.importorskip("numpy") +if m.long_double_and_double_have_same_size: + # Determined by the compiler used to build the pybind11 tests + # (e.g. MSVC gets here, but MinGW might not). + np_float128 = None + np_complex256 = None +else: + # Determined by the compiler used to build numpy (e.g. MinGW). + np_float128 = getattr(np, *["float128"] * 2) + np_complex256 = getattr(np, *["complex256"] * 2) + +CPP_NAME_FORMAT_NP_DTYPE_TABLE = [ + ("PyObject *", "O", object), + ("bool", "?", np.bool_), + ("std::int8_t", "b", np.int8), + ("std::uint8_t", "B", np.uint8), + ("std::int16_t", "h", np.int16), + ("std::uint16_t", "H", np.uint16), + ("std::int32_t", "i", np.int32), + ("std::uint32_t", "I", np.uint32), + ("std::int64_t", "q", np.int64), + ("std::uint64_t", "Q", np.uint64), + ("float", "f", np.float32), + ("double", "d", np.float64), + ("long double", "g", np_float128), + ("std::complex", "Zf", np.complex64), + ("std::complex", "Zd", np.complex128), + ("std::complex", "Zg", np_complex256), +] +CPP_NAME_FORMAT_TABLE = [ + (cpp_name, format) + for cpp_name, format, np_dtype in CPP_NAME_FORMAT_NP_DTYPE_TABLE + if np_dtype is not None +] +CPP_NAME_NP_DTYPE_TABLE = [ + (cpp_name, np_dtype) for cpp_name, _, np_dtype in CPP_NAME_FORMAT_NP_DTYPE_TABLE +] + + +@pytest.mark.parametrize(("cpp_name", "np_dtype"), CPP_NAME_NP_DTYPE_TABLE) +def test_format_descriptor_format_buffer_info_equiv(cpp_name, np_dtype): + if np_dtype is None: + pytest.skip( + f"cpp_name=`{cpp_name}`: `long double` and `double` have same size." + ) + if isinstance(np_dtype, str): + pytest.skip(f"np.{np_dtype} does not exist.") + np_array = np.array([], dtype=np_dtype) + for other_cpp_name, expected_format in CPP_NAME_FORMAT_TABLE: + format, np_array_is_matching = m.format_descriptor_format_buffer_info_equiv( + other_cpp_name, np_array + ) + assert format == expected_format + if other_cpp_name == cpp_name: + assert np_array_is_matching + else: + assert not np_array_is_matching + def test_from_python(): with pytest.raises(RuntimeError) as excinfo: @@ -54,7 +111,8 @@ def test_to_python(): mat2 = np.array(mat, copy=False) assert mat2.shape == (5, 4) assert abs(mat2).sum() == 11 - assert mat2[2, 3] == 4 and mat2[3, 2] == 7 + assert mat2[2, 3] == 4 + assert mat2[3, 2] == 7 mat2[2, 3] = 5 assert mat2[2, 3] == 5 diff --git a/pybind11/tests/test_builtin_casters.cpp b/pybind11/tests/test_builtin_casters.cpp index 6529f47d0..0623b85dc 100644 --- a/pybind11/tests/test_builtin_casters.cpp +++ b/pybind11/tests/test_builtin_casters.cpp @@ -73,6 +73,9 @@ PYBIND11_NAMESPACE_END(detail) PYBIND11_NAMESPACE_END(pybind11) TEST_SUBMODULE(builtin_casters, m) { + PYBIND11_WARNING_PUSH + PYBIND11_WARNING_DISABLE_MSVC(4127) + // test_simple_string m.def("string_roundtrip", [](const char *s) { return s; }); @@ -86,7 +89,7 @@ TEST_SUBMODULE(builtin_casters, m) { std::wstring wstr; wstr.push_back(0x61); // a wstr.push_back(0x2e18); // ⸘ - if (PYBIND11_SILENCE_MSVC_C4127(sizeof(wchar_t) == 2)) { + if (sizeof(wchar_t) == 2) { wstr.push_back(mathbfA16_1); wstr.push_back(mathbfA16_2); } // 𝐀, utf16 @@ -113,7 +116,7 @@ TEST_SUBMODULE(builtin_casters, m) { // Under Python 2.7, invalid unicode UTF-32 characters didn't appear to trigger // UnicodeDecodeError m.def("bad_utf32_string", [=]() { return std::u32string({a32, char32_t(0xd800), z32}); }); - if (PYBIND11_SILENCE_MSVC_C4127(sizeof(wchar_t) == 2)) { + if (sizeof(wchar_t) == 2) { m.def("bad_wchar_string", [=]() { return std::wstring({wchar_t(0x61), wchar_t(0xd800)}); }); @@ -266,9 +269,14 @@ TEST_SUBMODULE(builtin_casters, m) { }); m.def("lvalue_nested", []() -> const decltype(lvnested) & { return lvnested; }); - static std::pair int_string_pair{2, "items"}; m.def( - "int_string_pair", []() { return &int_string_pair; }, py::return_value_policy::reference); + "int_string_pair", + []() { + // Using no-destructor idiom to side-step warnings from overzealous compilers. + static auto *int_string_pair = new std::pair{2, "items"}; + return int_string_pair; + }, + py::return_value_policy::reference); // test_builtins_cast_return_none m.def("return_none_string", []() -> std::string * { return nullptr; }); @@ -379,4 +387,6 @@ TEST_SUBMODULE(builtin_casters, m) { m.def("takes_const_ref", [](const ConstRefCasted &x) { return x.tag; }); m.def("takes_const_ref_wrap", [](std::reference_wrapper x) { return x.get().tag; }); + + PYBIND11_WARNING_POP } diff --git a/pybind11/tests/test_builtin_casters.py b/pybind11/tests/test_builtin_casters.py index d38ae6802..b1f57bdd9 100644 --- a/pybind11/tests/test_builtin_casters.py +++ b/pybind11/tests/test_builtin_casters.py @@ -126,8 +126,8 @@ def test_bytes_to_string(): assert m.strlen(b"hi") == 2 assert m.string_length(b"world") == 5 - assert m.string_length("a\x00b".encode()) == 3 - assert m.strlen("a\x00b".encode()) == 1 # C-string limitation + assert m.string_length(b"a\x00b") == 3 + assert m.strlen(b"a\x00b") == 1 # C-string limitation # passing in a utf8 encoded string should work assert m.string_length("💩".encode()) == 4 @@ -421,13 +421,15 @@ def test_reference_wrapper(): a2 = m.refwrap_list(copy=True) assert [x.value for x in a1] == [2, 3] assert [x.value for x in a2] == [2, 3] - assert not a1[0] is a2[0] and not a1[1] is a2[1] + assert a1[0] is not a2[0] + assert a1[1] is not a2[1] b1 = m.refwrap_list(copy=False) b2 = m.refwrap_list(copy=False) assert [x.value for x in b1] == [1, 2] assert [x.value for x in b2] == [1, 2] - assert b1[0] is b2[0] and b1[1] is b2[1] + assert b1[0] is b2[0] + assert b1[1] is b2[1] assert m.refwrap_iiw(IncType(5)) == 5 assert m.refwrap_call_iiw(IncType(10), m.refwrap_iiw) == [10, 10, 10, 10] diff --git a/pybind11/tests/test_callbacks.cpp b/pybind11/tests/test_callbacks.cpp index 92b8053de..2fd05dec7 100644 --- a/pybind11/tests/test_callbacks.cpp +++ b/pybind11/tests/test_callbacks.cpp @@ -240,4 +240,41 @@ TEST_SUBMODULE(callbacks, m) { f(); } }); + + auto *custom_def = []() { + static PyMethodDef def; + def.ml_name = "example_name"; + def.ml_doc = "Example doc"; + def.ml_meth = [](PyObject *, PyObject *args) -> PyObject * { + if (PyTuple_Size(args) != 1) { + throw std::runtime_error("Invalid number of arguments for example_name"); + } + PyObject *first = PyTuple_GetItem(args, 0); + if (!PyLong_Check(first)) { + throw std::runtime_error("Invalid argument to example_name"); + } + auto result = py::cast(PyLong_AsLong(first) * 9); + return result.release().ptr(); + }; + def.ml_flags = METH_VARARGS; + return &def; + }(); + + // rec_capsule with name that has the same value (but not pointer) as our internal one + // This capsule should be detected by our code as foreign and not inspected as the pointers + // shouldn't match + constexpr const char *rec_capsule_name + = pybind11::detail::internals_function_record_capsule_name; + py::capsule rec_capsule(std::malloc(1), [](void *data) { std::free(data); }); + rec_capsule.set_name(rec_capsule_name); + m.add_object("custom_function", PyCFunction_New(custom_def, rec_capsule.ptr())); + + // This test requires a new ABI version to pass +#if PYBIND11_INTERNALS_VERSION > 4 + // rec_capsule with nullptr name + py::capsule rec_capsule2(std::malloc(1), [](void *data) { std::free(data); }); + m.add_object("custom_function2", PyCFunction_New(custom_def, rec_capsule2.ptr())); +#else + m.add_object("custom_function2", py::none()); +#endif } diff --git a/pybind11/tests/test_callbacks.py b/pybind11/tests/test_callbacks.py index 0b1047bbf..4a652f53e 100644 --- a/pybind11/tests/test_callbacks.py +++ b/pybind11/tests/test_callbacks.py @@ -5,6 +5,7 @@ import pytest import env # noqa: F401 from pybind11_tests import callbacks as m +from pybind11_tests import detailed_error_messages_enabled def test_callbacks(): @@ -70,11 +71,20 @@ def test_keyword_args_and_generalized_unpacking(): with pytest.raises(RuntimeError) as excinfo: m.test_arg_conversion_error1(f) - assert "Unable to convert call argument" in str(excinfo.value) + assert str(excinfo.value) == "Unable to convert call argument " + ( + "'1' of type 'UnregisteredType' to Python object" + if detailed_error_messages_enabled + else "'1' to Python object (#define PYBIND11_DETAILED_ERROR_MESSAGES or compile in debug mode for details)" + ) with pytest.raises(RuntimeError) as excinfo: m.test_arg_conversion_error2(f) - assert "Unable to convert call argument" in str(excinfo.value) + assert str(excinfo.value) == "Unable to convert call argument " + ( + "'expected_name' of type 'UnregisteredType' to Python object" + if detailed_error_messages_enabled + else "'expected_name' to Python object " + "(#define PYBIND11_DETAILED_ERROR_MESSAGES or compile in debug mode for details)" + ) def test_lambda_closure_cleanup(): @@ -193,3 +203,16 @@ def test_callback_num_times(): if len(rates) > 1: print("Min Mean Max") print(f"{min(rates):6.3f} {sum(rates) / len(rates):6.3f} {max(rates):6.3f}") + + +def test_custom_func(): + assert m.custom_function(4) == 36 + assert m.roundtrip(m.custom_function)(4) == 36 + + +@pytest.mark.skipif( + m.custom_function2 is None, reason="Current PYBIND11_INTERNALS_VERSION too low" +) +def test_custom_func2(): + assert m.custom_function2(3) == 27 + assert m.roundtrip(m.custom_function2)(3) == 27 diff --git a/pybind11/tests/test_chrono.py b/pybind11/tests/test_chrono.py index 7f47b37a2..a29316c38 100644 --- a/pybind11/tests/test_chrono.py +++ b/pybind11/tests/test_chrono.py @@ -7,7 +7,6 @@ from pybind11_tests import chrono as m def test_chrono_system_clock(): - # Get the time from both c++ and datetime date0 = datetime.datetime.today() date1 = m.test_chrono1() @@ -122,7 +121,6 @@ def test_chrono_system_clock_roundtrip_time(time1, tz, monkeypatch): def test_chrono_duration_roundtrip(): - # Get the difference between two times (a timedelta) date1 = datetime.datetime.today() date2 = datetime.datetime.today() @@ -143,7 +141,6 @@ def test_chrono_duration_roundtrip(): def test_chrono_duration_subtraction_equivalence(): - date1 = datetime.datetime.today() date2 = datetime.datetime.today() @@ -154,7 +151,6 @@ def test_chrono_duration_subtraction_equivalence(): def test_chrono_duration_subtraction_equivalence_date(): - date1 = datetime.date.today() date2 = datetime.date.today() diff --git a/pybind11/tests/test_class.cpp b/pybind11/tests/test_class.cpp index c8b8071dc..7241bc881 100644 --- a/pybind11/tests/test_class.cpp +++ b/pybind11/tests/test_class.cpp @@ -22,10 +22,8 @@ #include -#if defined(_MSC_VER) -# pragma warning(disable : 4324) +PYBIND11_WARNING_DISABLE_MSVC(4324) // warning C4324: structure was padded due to alignment specifier -#endif // test_brace_initialization struct NoBraceInitialization { @@ -36,7 +34,29 @@ struct NoBraceInitialization { std::vector vec; }; +namespace test_class { +namespace pr4220_tripped_over_this { // PR #4227 + +template +struct SoEmpty {}; + +template +std::string get_msg(const T &) { + return "This is really only meant to exercise successful compilation."; +} + +using Empty0 = SoEmpty<0x0>; + +void bind_empty0(py::module_ &m) { + py::class_(m, "Empty0").def(py::init<>()).def("get_msg", get_msg); +} + +} // namespace pr4220_tripped_over_this +} // namespace test_class + TEST_SUBMODULE(class_, m) { + m.def("obj_class_name", [](py::handle obj) { return py::detail::obj_class_name(obj.ptr()); }); + // test_instance struct NoConstructor { NoConstructor() = default; @@ -65,7 +85,7 @@ TEST_SUBMODULE(class_, m) { .def_static("new_instance", &NoConstructor::new_instance, "Return an instance"); py::class_(m, "NoConstructorNew") - .def(py::init([](const NoConstructorNew &self) { return self; })) // Need a NOOP __init__ + .def(py::init([]() { return nullptr; })) // Need a NOOP __init__ .def_static("__new__", [](const py::object &) { return NoConstructorNew::new_instance(); }); @@ -364,6 +384,8 @@ TEST_SUBMODULE(class_, m) { protected: virtual int foo() const { return value; } + virtual void *void_foo() { return static_cast(&value); } + virtual void *get_self() { return static_cast(this); } private: int value = 42; @@ -372,6 +394,8 @@ TEST_SUBMODULE(class_, m) { class TrampolineB : public ProtectedB { public: int foo() const override { PYBIND11_OVERRIDE(int, ProtectedB, foo, ); } + void *void_foo() override { PYBIND11_OVERRIDE(void *, ProtectedB, void_foo, ); } + void *get_self() override { PYBIND11_OVERRIDE(void *, ProtectedB, get_self, ); } }; class PublicistB : public ProtectedB { @@ -381,11 +405,23 @@ TEST_SUBMODULE(class_, m) { // (in Debug builds only, tested with icpc (ICC) 2021.1 Beta 20200827) ~PublicistB() override{}; // NOLINT(modernize-use-equals-default) using ProtectedB::foo; + using ProtectedB::get_self; + using ProtectedB::void_foo; }; + m.def("read_foo", [](const void *original) { + const int *ptr = reinterpret_cast(original); + return *ptr; + }); + + m.def("pointers_equal", + [](const void *original, const void *comparison) { return original == comparison; }); + py::class_(m, "ProtectedB") .def(py::init<>()) - .def("foo", &PublicistB::foo); + .def("foo", &PublicistB::foo) + .def("void_foo", &PublicistB::void_foo) + .def("get_self", &PublicistB::get_self); // test_brace_initialization struct BraceInitialization { @@ -517,6 +553,8 @@ TEST_SUBMODULE(class_, m) { py::class_(gt, "OtherDuplicateNested"); py::class_(gt, "YetAnotherDuplicateNested"); }); + + test_class::pr4220_tripped_over_this::bind_empty0(m); } template diff --git a/pybind11/tests/test_class.py b/pybind11/tests/test_class.py index ff9196f0f..ee7467cf8 100644 --- a/pybind11/tests/test_class.py +++ b/pybind11/tests/test_class.py @@ -1,10 +1,16 @@ import pytest -import env # noqa: F401 +import env from pybind11_tests import ConstructorStats, UserType from pybind11_tests import class_ as m +def test_obj_class_name(): + expected_name = "UserType" if env.PYPY else "pybind11_tests.UserType" + assert m.obj_class_name(UserType(1)) == expected_name + assert m.obj_class_name(UserType) == expected_name + + def test_repr(): assert "pybind11_type" in repr(type(UserType)) assert "UserType" in repr(UserType) @@ -23,7 +29,7 @@ def test_instance(msg): assert cstats.alive() == 0 -def test_instance_new(msg): +def test_instance_new(): instance = m.NoConstructorNew() # .__new__(m.NoConstructor.__class__) cstats = ConstructorStats.get(m.NoConstructorNew) assert cstats.alive() == 1 @@ -176,7 +182,6 @@ def test_inheritance(msg): def test_inheritance_init(msg): - # Single base class Python(m.Pet): def __init__(self): @@ -213,7 +218,7 @@ def test_automatic_upcasting(): def test_isinstance(): - objects = [tuple(), dict(), m.Pet("Polly", "parrot")] + [m.Dog("Molly")] * 4 + objects = [(), {}, m.Pet("Polly", "parrot")] + [m.Dog("Molly")] * 4 expected = (True, True, True, True, True, False, False) assert m.check_instances(objects) == expected @@ -313,6 +318,8 @@ def test_bind_protected_functions(): b = m.ProtectedB() assert b.foo() == 42 + assert m.read_foo(b.void_foo()) == 42 + assert m.pointers_equal(b.get_self(), b) class C(m.ProtectedB): def __init__(self): @@ -417,7 +424,7 @@ def test_exception_rvalue_abort(): # https://github.com/pybind/pybind11/issues/1568 -def test_multiple_instances_with_same_pointer(capture): +def test_multiple_instances_with_same_pointer(): n = 100 instances = [m.SamePointer() for _ in range(n)] for i in range(n): @@ -469,3 +476,10 @@ def test_register_duplicate_class(): m.register_duplicate_nested_class_type(ClassScope) expected = 'generic_type: type "YetAnotherDuplicateNested" is already registered!' assert str(exc_info.value) == expected + + +def test_pr4220_tripped_over_this(): + assert ( + m.Empty0().get_msg() + == "This is really only meant to exercise successful compilation." + ) diff --git a/pybind11/tests/test_cmake_build/CMakeLists.txt b/pybind11/tests/test_cmake_build/CMakeLists.txt index 8bfaa386a..e5aa975cf 100644 --- a/pybind11/tests/test_cmake_build/CMakeLists.txt +++ b/pybind11/tests/test_cmake_build/CMakeLists.txt @@ -1,6 +1,3 @@ -# Built-in in CMake 3.5+ -include(CMakeParseArguments) - add_custom_target(test_cmake_build) function(pybind11_add_build_test name) diff --git a/pybind11/tests/test_cmake_build/installed_embed/CMakeLists.txt b/pybind11/tests/test_cmake_build/installed_embed/CMakeLists.txt index f7d693998..d9dcb45e4 100644 --- a/pybind11/tests/test_cmake_build/installed_embed/CMakeLists.txt +++ b/pybind11/tests/test_cmake_build/installed_embed/CMakeLists.txt @@ -1,12 +1,12 @@ -cmake_minimum_required(VERSION 3.4) +cmake_minimum_required(VERSION 3.5) -# The `cmake_minimum_required(VERSION 3.4...3.18)` syntax does not work with +# The `cmake_minimum_required(VERSION 3.5...3.26)` syntax does not work with # some versions of VS that have a patched CMake 3.11. This forces us to emulate # the behavior using the following workaround: -if(${CMAKE_VERSION} VERSION_LESS 3.18) +if(${CMAKE_VERSION} VERSION_LESS 3.26) cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}) else() - cmake_policy(VERSION 3.18) + cmake_policy(VERSION 3.26) endif() project(test_installed_embed CXX) diff --git a/pybind11/tests/test_cmake_build/installed_function/CMakeLists.txt b/pybind11/tests/test_cmake_build/installed_function/CMakeLists.txt index d7ca4db55..2f4f64275 100644 --- a/pybind11/tests/test_cmake_build/installed_function/CMakeLists.txt +++ b/pybind11/tests/test_cmake_build/installed_function/CMakeLists.txt @@ -1,13 +1,13 @@ -cmake_minimum_required(VERSION 3.4) +cmake_minimum_required(VERSION 3.5) project(test_installed_module CXX) -# The `cmake_minimum_required(VERSION 3.4...3.18)` syntax does not work with +# The `cmake_minimum_required(VERSION 3.5...3.26)` syntax does not work with # some versions of VS that have a patched CMake 3.11. This forces us to emulate # the behavior using the following workaround: -if(${CMAKE_VERSION} VERSION_LESS 3.18) +if(${CMAKE_VERSION} VERSION_LESS 3.26) cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}) else() - cmake_policy(VERSION 3.18) + cmake_policy(VERSION 3.26) endif() project(test_installed_function CXX) diff --git a/pybind11/tests/test_cmake_build/installed_target/CMakeLists.txt b/pybind11/tests/test_cmake_build/installed_target/CMakeLists.txt index bc5e101f1..a981e236f 100644 --- a/pybind11/tests/test_cmake_build/installed_target/CMakeLists.txt +++ b/pybind11/tests/test_cmake_build/installed_target/CMakeLists.txt @@ -1,12 +1,12 @@ -cmake_minimum_required(VERSION 3.4) +cmake_minimum_required(VERSION 3.5) -# The `cmake_minimum_required(VERSION 3.4...3.18)` syntax does not work with +# The `cmake_minimum_required(VERSION 3.5...3.26)` syntax does not work with # some versions of VS that have a patched CMake 3.11. This forces us to emulate # the behavior using the following workaround: -if(${CMAKE_VERSION} VERSION_LESS 3.18) +if(${CMAKE_VERSION} VERSION_LESS 3.26) cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}) else() - cmake_policy(VERSION 3.18) + cmake_policy(VERSION 3.26) endif() project(test_installed_target CXX) diff --git a/pybind11/tests/test_cmake_build/subdirectory_embed/CMakeLists.txt b/pybind11/tests/test_cmake_build/subdirectory_embed/CMakeLists.txt index 58cdd7cfd..f286746b9 100644 --- a/pybind11/tests/test_cmake_build/subdirectory_embed/CMakeLists.txt +++ b/pybind11/tests/test_cmake_build/subdirectory_embed/CMakeLists.txt @@ -1,12 +1,12 @@ -cmake_minimum_required(VERSION 3.4) +cmake_minimum_required(VERSION 3.5) -# The `cmake_minimum_required(VERSION 3.4...3.18)` syntax does not work with +# The `cmake_minimum_required(VERSION 3.5...3.26)` syntax does not work with # some versions of VS that have a patched CMake 3.11. This forces us to emulate # the behavior using the following workaround: -if(${CMAKE_VERSION} VERSION_LESS 3.18) +if(${CMAKE_VERSION} VERSION_LESS 3.26) cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}) else() - cmake_policy(VERSION 3.18) + cmake_policy(VERSION 3.26) endif() project(test_subdirectory_embed CXX) diff --git a/pybind11/tests/test_cmake_build/subdirectory_function/CMakeLists.txt b/pybind11/tests/test_cmake_build/subdirectory_function/CMakeLists.txt index 01557c439..275a75c0b 100644 --- a/pybind11/tests/test_cmake_build/subdirectory_function/CMakeLists.txt +++ b/pybind11/tests/test_cmake_build/subdirectory_function/CMakeLists.txt @@ -1,12 +1,12 @@ -cmake_minimum_required(VERSION 3.4) +cmake_minimum_required(VERSION 3.5) -# The `cmake_minimum_required(VERSION 3.4...3.18)` syntax does not work with +# The `cmake_minimum_required(VERSION 3.5...3.26)` syntax does not work with # some versions of VS that have a patched CMake 3.11. This forces us to emulate # the behavior using the following workaround: -if(${CMAKE_VERSION} VERSION_LESS 3.18) +if(${CMAKE_VERSION} VERSION_LESS 3.26) cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}) else() - cmake_policy(VERSION 3.18) + cmake_policy(VERSION 3.26) endif() project(test_subdirectory_function CXX) diff --git a/pybind11/tests/test_cmake_build/subdirectory_target/CMakeLists.txt b/pybind11/tests/test_cmake_build/subdirectory_target/CMakeLists.txt index ba82fdee2..37bb2c56e 100644 --- a/pybind11/tests/test_cmake_build/subdirectory_target/CMakeLists.txt +++ b/pybind11/tests/test_cmake_build/subdirectory_target/CMakeLists.txt @@ -1,12 +1,12 @@ -cmake_minimum_required(VERSION 3.4) +cmake_minimum_required(VERSION 3.5) -# The `cmake_minimum_required(VERSION 3.4...3.18)` syntax does not work with +# The `cmake_minimum_required(VERSION 3.5...3.26)` syntax does not work with # some versions of VS that have a patched CMake 3.11. This forces us to emulate # the behavior using the following workaround: -if(${CMAKE_VERSION} VERSION_LESS 3.18) +if(${CMAKE_VERSION} VERSION_LESS 3.26) cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}) else() - cmake_policy(VERSION 3.18) + cmake_policy(VERSION 3.26) endif() project(test_subdirectory_target CXX) diff --git a/pybind11/tests/test_const_name.py b/pybind11/tests/test_const_name.py index 10b0caee2..a145f0bbb 100644 --- a/pybind11/tests/test_const_name.py +++ b/pybind11/tests/test_const_name.py @@ -3,9 +3,9 @@ import pytest from pybind11_tests import const_name as m -@pytest.mark.parametrize("func", (m.const_name_tests, m.underscore_tests)) +@pytest.mark.parametrize("func", [m.const_name_tests, m.underscore_tests]) @pytest.mark.parametrize( - "selector, expected", + ("selector", "expected"), enumerate( ( "", diff --git a/pybind11/tests/test_constants_and_functions.cpp b/pybind11/tests/test_constants_and_functions.cpp index 1918a429c..312edca9e 100644 --- a/pybind11/tests/test_constants_and_functions.cpp +++ b/pybind11/tests/test_constants_and_functions.cpp @@ -52,15 +52,12 @@ int f1(int x) noexcept { return x + 1; } #endif int f2(int x) noexcept(true) { return x + 2; } int f3(int x) noexcept(false) { return x + 3; } -#if defined(__GNUG__) && !defined(__INTEL_COMPILER) -# pragma GCC diagnostic push -# pragma GCC diagnostic ignored "-Wdeprecated" -#endif +PYBIND11_WARNING_PUSH +PYBIND11_WARNING_DISABLE_GCC("-Wdeprecated") +PYBIND11_WARNING_DISABLE_CLANG("-Wdeprecated") // NOLINTNEXTLINE(modernize-use-noexcept) int f4(int x) throw() { return x + 4; } // Deprecated equivalent to noexcept(true) -#if defined(__GNUG__) && !defined(__INTEL_COMPILER) -# pragma GCC diagnostic pop -#endif +PYBIND11_WARNING_POP struct C { int m1(int x) noexcept { return x - 1; } int m2(int x) const noexcept { return x - 2; } @@ -68,17 +65,14 @@ struct C { int m4(int x) const noexcept(true) { return x - 4; } int m5(int x) noexcept(false) { return x - 5; } int m6(int x) const noexcept(false) { return x - 6; } -#if defined(__GNUG__) && !defined(__INTEL_COMPILER) -# pragma GCC diagnostic push -# pragma GCC diagnostic ignored "-Wdeprecated" -#endif + PYBIND11_WARNING_PUSH + PYBIND11_WARNING_DISABLE_GCC("-Wdeprecated") + PYBIND11_WARNING_DISABLE_CLANG("-Wdeprecated") // NOLINTNEXTLINE(modernize-use-noexcept) int m7(int x) throw() { return x - 7; } // NOLINTNEXTLINE(modernize-use-noexcept) int m8(int x) const throw() { return x - 8; } -#if defined(__GNUG__) && !defined(__INTEL_COMPILER) -# pragma GCC diagnostic pop -#endif + PYBIND11_WARNING_POP }; } // namespace test_exc_sp @@ -126,14 +120,12 @@ TEST_SUBMODULE(constants_and_functions, m) { .def("m8", &C::m8); m.def("f1", f1); m.def("f2", f2); -#if defined(__INTEL_COMPILER) -# pragma warning push -# pragma warning disable 878 // incompatible exception specifications -#endif + + PYBIND11_WARNING_PUSH + PYBIND11_WARNING_DISABLE_INTEL(878) // incompatible exception specifications m.def("f3", f3); -#if defined(__INTEL_COMPILER) -# pragma warning pop -#endif + PYBIND11_WARNING_POP + m.def("f4", f4); // test_function_record_leaks @@ -156,4 +148,7 @@ TEST_SUBMODULE(constants_and_functions, m) { py::arg_v("y", 42, ""), py::arg_v("z", default_value)); }); + + // test noexcept(true) lambda (#4565) + m.def("l1", []() noexcept(true) { return 0; }); } diff --git a/pybind11/tests/test_constants_and_functions.py b/pybind11/tests/test_constants_and_functions.py index 5da0b84b8..a1142461c 100644 --- a/pybind11/tests/test_constants_and_functions.py +++ b/pybind11/tests/test_constants_and_functions.py @@ -50,3 +50,7 @@ def test_function_record_leaks(): m.register_large_capture_with_invalid_arguments(m) with pytest.raises(RuntimeError): m.register_with_raising_repr(m, RaisingRepr()) + + +def test_noexcept_lambda(): + assert m.l1() == 0 diff --git a/pybind11/tests/test_copy_move.cpp b/pybind11/tests/test_copy_move.cpp index 28c244564..f54733550 100644 --- a/pybind11/tests/test_copy_move.cpp +++ b/pybind11/tests/test_copy_move.cpp @@ -13,6 +13,8 @@ #include "constructor_stats.h" #include "pybind11_tests.h" +#include + template struct empty { static const derived &get_one() { return instance_; } @@ -293,3 +295,239 @@ TEST_SUBMODULE(copy_move_policies, m) { // Make sure that cast from pytype rvalue to other pytype works m.def("get_pytype_rvalue_castissue", [](double i) { return py::float_(i).cast(); }); } + +/* + * Rest of the file: + * static_assert based tests for pybind11 adaptations of + * std::is_move_constructible, std::is_copy_constructible and + * std::is_copy_assignable (no adaptation of std::is_move_assignable). + * Difference between pybind11 and std traits: pybind11 traits will also check + * the contained value_types. + */ + +struct NotMovable { + NotMovable() = default; + NotMovable(NotMovable const &) = default; + NotMovable(NotMovable &&) = delete; + NotMovable &operator=(NotMovable const &) = default; + NotMovable &operator=(NotMovable &&) = delete; +}; +static_assert(!std::is_move_constructible::value, + "!std::is_move_constructible::value"); +static_assert(std::is_copy_constructible::value, + "std::is_copy_constructible::value"); +static_assert(!pybind11::detail::is_move_constructible::value, + "!pybind11::detail::is_move_constructible::value"); +static_assert(pybind11::detail::is_copy_constructible::value, + "pybind11::detail::is_copy_constructible::value"); +static_assert(!std::is_move_assignable::value, + "!std::is_move_assignable::value"); +static_assert(std::is_copy_assignable::value, + "std::is_copy_assignable::value"); +// pybind11 does not have this +// static_assert(!pybind11::detail::is_move_assignable::value, +// "!pybind11::detail::is_move_assignable::value"); +static_assert(pybind11::detail::is_copy_assignable::value, + "pybind11::detail::is_copy_assignable::value"); + +struct NotCopyable { + NotCopyable() = default; + NotCopyable(NotCopyable const &) = delete; + NotCopyable(NotCopyable &&) = default; + NotCopyable &operator=(NotCopyable const &) = delete; + NotCopyable &operator=(NotCopyable &&) = default; +}; +static_assert(std::is_move_constructible::value, + "std::is_move_constructible::value"); +static_assert(!std::is_copy_constructible::value, + "!std::is_copy_constructible::value"); +static_assert(pybind11::detail::is_move_constructible::value, + "pybind11::detail::is_move_constructible::value"); +static_assert(!pybind11::detail::is_copy_constructible::value, + "!pybind11::detail::is_copy_constructible::value"); +static_assert(std::is_move_assignable::value, + "std::is_move_assignable::value"); +static_assert(!std::is_copy_assignable::value, + "!std::is_copy_assignable::value"); +// pybind11 does not have this +// static_assert(!pybind11::detail::is_move_assignable::value, +// "!pybind11::detail::is_move_assignable::value"); +static_assert(!pybind11::detail::is_copy_assignable::value, + "!pybind11::detail::is_copy_assignable::value"); + +struct NotCopyableNotMovable { + NotCopyableNotMovable() = default; + NotCopyableNotMovable(NotCopyableNotMovable const &) = delete; + NotCopyableNotMovable(NotCopyableNotMovable &&) = delete; + NotCopyableNotMovable &operator=(NotCopyableNotMovable const &) = delete; + NotCopyableNotMovable &operator=(NotCopyableNotMovable &&) = delete; +}; +static_assert(!std::is_move_constructible::value, + "!std::is_move_constructible::value"); +static_assert(!std::is_copy_constructible::value, + "!std::is_copy_constructible::value"); +static_assert(!pybind11::detail::is_move_constructible::value, + "!pybind11::detail::is_move_constructible::value"); +static_assert(!pybind11::detail::is_copy_constructible::value, + "!pybind11::detail::is_copy_constructible::value"); +static_assert(!std::is_move_assignable::value, + "!std::is_move_assignable::value"); +static_assert(!std::is_copy_assignable::value, + "!std::is_copy_assignable::value"); +// pybind11 does not have this +// static_assert(!pybind11::detail::is_move_assignable::value, +// "!pybind11::detail::is_move_assignable::value"); +static_assert(!pybind11::detail::is_copy_assignable::value, + "!pybind11::detail::is_copy_assignable::value"); + +struct NotMovableVector : std::vector {}; +static_assert(std::is_move_constructible::value, + "std::is_move_constructible::value"); +static_assert(std::is_copy_constructible::value, + "std::is_copy_constructible::value"); +static_assert(!pybind11::detail::is_move_constructible::value, + "!pybind11::detail::is_move_constructible::value"); +static_assert(pybind11::detail::is_copy_constructible::value, + "pybind11::detail::is_copy_constructible::value"); +static_assert(std::is_move_assignable::value, + "std::is_move_assignable::value"); +static_assert(std::is_copy_assignable::value, + "std::is_copy_assignable::value"); +// pybind11 does not have this +// static_assert(!pybind11::detail::is_move_assignable::value, +// "!pybind11::detail::is_move_assignable::value"); +static_assert(pybind11::detail::is_copy_assignable::value, + "pybind11::detail::is_copy_assignable::value"); + +struct NotCopyableVector : std::vector {}; +static_assert(std::is_move_constructible::value, + "std::is_move_constructible::value"); +static_assert(std::is_copy_constructible::value, + "std::is_copy_constructible::value"); +static_assert(pybind11::detail::is_move_constructible::value, + "pybind11::detail::is_move_constructible::value"); +static_assert(!pybind11::detail::is_copy_constructible::value, + "!pybind11::detail::is_copy_constructible::value"); +static_assert(std::is_move_assignable::value, + "std::is_move_assignable::value"); +static_assert(std::is_copy_assignable::value, + "std::is_copy_assignable::value"); +// pybind11 does not have this +// static_assert(!pybind11::detail::is_move_assignable::value, +// "!pybind11::detail::is_move_assignable::value"); +static_assert(!pybind11::detail::is_copy_assignable::value, + "!pybind11::detail::is_copy_assignable::value"); + +struct NotCopyableNotMovableVector : std::vector {}; +static_assert(std::is_move_constructible::value, + "std::is_move_constructible::value"); +static_assert(std::is_copy_constructible::value, + "std::is_copy_constructible::value"); +static_assert(!pybind11::detail::is_move_constructible::value, + "!pybind11::detail::is_move_constructible::value"); +static_assert(!pybind11::detail::is_copy_constructible::value, + "!pybind11::detail::is_copy_constructible::value"); +static_assert(std::is_move_assignable::value, + "std::is_move_assignable::value"); +static_assert(std::is_copy_assignable::value, + "std::is_copy_assignable::value"); +// pybind11 does not have this +// static_assert(!pybind11::detail::is_move_assignable::value, +// "!pybind11::detail::is_move_assignable::value"); +static_assert(!pybind11::detail::is_copy_assignable::value, + "!pybind11::detail::is_copy_assignable::value"); + +struct NotMovableMap : std::map {}; +static_assert(std::is_move_constructible::value, + "std::is_move_constructible::value"); +static_assert(std::is_copy_constructible::value, + "std::is_copy_constructible::value"); +static_assert(!pybind11::detail::is_move_constructible::value, + "!pybind11::detail::is_move_constructible::value"); +static_assert(pybind11::detail::is_copy_constructible::value, + "pybind11::detail::is_copy_constructible::value"); +static_assert(std::is_move_assignable::value, + "std::is_move_assignable::value"); +static_assert(std::is_copy_assignable::value, + "std::is_copy_assignable::value"); +// pybind11 does not have this +// static_assert(!pybind11::detail::is_move_assignable::value, +// "!pybind11::detail::is_move_assignable::value"); +static_assert(pybind11::detail::is_copy_assignable::value, + "pybind11::detail::is_copy_assignable::value"); + +struct NotCopyableMap : std::map {}; +static_assert(std::is_move_constructible::value, + "std::is_move_constructible::value"); +static_assert(std::is_copy_constructible::value, + "std::is_copy_constructible::value"); +static_assert(pybind11::detail::is_move_constructible::value, + "pybind11::detail::is_move_constructible::value"); +static_assert(!pybind11::detail::is_copy_constructible::value, + "!pybind11::detail::is_copy_constructible::value"); +static_assert(std::is_move_assignable::value, + "std::is_move_assignable::value"); +static_assert(std::is_copy_assignable::value, + "std::is_copy_assignable::value"); +// pybind11 does not have this +// static_assert(!pybind11::detail::is_move_assignable::value, +// "!pybind11::detail::is_move_assignable::value"); +static_assert(!pybind11::detail::is_copy_assignable::value, + "!pybind11::detail::is_copy_assignable::value"); + +struct NotCopyableNotMovableMap : std::map {}; +static_assert(std::is_move_constructible::value, + "std::is_move_constructible::value"); +static_assert(std::is_copy_constructible::value, + "std::is_copy_constructible::value"); +static_assert(!pybind11::detail::is_move_constructible::value, + "!pybind11::detail::is_move_constructible::value"); +static_assert(!pybind11::detail::is_copy_constructible::value, + "!pybind11::detail::is_copy_constructible::value"); +static_assert(std::is_move_assignable::value, + "std::is_move_assignable::value"); +static_assert(std::is_copy_assignable::value, + "std::is_copy_assignable::value"); +// pybind11 does not have this +// static_assert(!pybind11::detail::is_move_assignable::value, +// "!pybind11::detail::is_move_assignable::value"); +static_assert(!pybind11::detail::is_copy_assignable::value, + "!pybind11::detail::is_copy_assignable::value"); + +struct RecursiveVector : std::vector {}; +static_assert(std::is_move_constructible::value, + "std::is_move_constructible::value"); +static_assert(std::is_copy_constructible::value, + "std::is_copy_constructible::value"); +static_assert(pybind11::detail::is_move_constructible::value, + "pybind11::detail::is_move_constructible::value"); +static_assert(pybind11::detail::is_copy_constructible::value, + "pybind11::detail::is_copy_constructible::value"); +static_assert(std::is_move_assignable::value, + "std::is_move_assignable::value"); +static_assert(std::is_copy_assignable::value, + "std::is_copy_assignable::value"); +// pybind11 does not have this +// static_assert(!pybind11::detail::is_move_assignable::value, +// "!pybind11::detail::is_move_assignable::value"); +static_assert(pybind11::detail::is_copy_assignable::value, + "pybind11::detail::is_copy_assignable::value"); + +struct RecursiveMap : std::map {}; +static_assert(std::is_move_constructible::value, + "std::is_move_constructible::value"); +static_assert(std::is_copy_constructible::value, + "std::is_copy_constructible::value"); +static_assert(pybind11::detail::is_move_constructible::value, + "pybind11::detail::is_move_constructible::value"); +static_assert(pybind11::detail::is_copy_constructible::value, + "pybind11::detail::is_copy_constructible::value"); +static_assert(std::is_move_assignable::value, + "std::is_move_assignable::value"); +static_assert(std::is_copy_assignable::value, + "std::is_copy_assignable::value"); +// pybind11 does not have this +// static_assert(!pybind11::detail::is_move_assignable::value, +// "!pybind11::detail::is_move_assignable::value"); +static_assert(pybind11::detail::is_copy_assignable::value, + "pybind11::detail::is_copy_assignable::value"); diff --git a/pybind11/tests/test_custom_type_casters.cpp b/pybind11/tests/test_custom_type_casters.cpp index 25540e368..b4af02a45 100644 --- a/pybind11/tests/test_custom_type_casters.cpp +++ b/pybind11/tests/test_custom_type_casters.cpp @@ -21,7 +21,7 @@ public: }; class ArgAlwaysConverts {}; -namespace pybind11 { +namespace PYBIND11_NAMESPACE { namespace detail { template <> struct type_caster { @@ -74,7 +74,7 @@ public: } }; } // namespace detail -} // namespace pybind11 +} // namespace PYBIND11_NAMESPACE // test_custom_caster_destruction class DestructionTester { @@ -92,7 +92,7 @@ public: return *this; } }; -namespace pybind11 { +namespace PYBIND11_NAMESPACE { namespace detail { template <> struct type_caster { @@ -104,7 +104,7 @@ struct type_caster { } }; } // namespace detail -} // namespace pybind11 +} // namespace PYBIND11_NAMESPACE // Define type caster outside of `pybind11::detail` and then alias it. namespace other_lib { @@ -112,7 +112,7 @@ struct MyType {}; // Corrupt `py` shorthand alias for surrounding context. namespace py {} // Corrupt unqualified relative `pybind11` namespace. -namespace pybind11 {} +namespace PYBIND11_NAMESPACE {} // Correct alias. namespace py_ = ::pybind11; // Define caster. This is effectively no-op, we only ensure it compiles and we @@ -127,12 +127,12 @@ struct my_caster { }; } // namespace other_lib // Effectively "alias" it into correct namespace (via inheritance). -namespace pybind11 { +namespace PYBIND11_NAMESPACE { namespace detail { template <> struct type_caster : public other_lib::my_caster {}; } // namespace detail -} // namespace pybind11 +} // namespace PYBIND11_NAMESPACE TEST_SUBMODULE(custom_type_casters, m) { // test_custom_type_casters diff --git a/pybind11/tests/test_custom_type_casters.py b/pybind11/tests/test_custom_type_casters.py index adfa6cf86..3a00ea964 100644 --- a/pybind11/tests/test_custom_type_casters.py +++ b/pybind11/tests/test_custom_type_casters.py @@ -94,12 +94,14 @@ def test_noconvert_args(msg): def test_custom_caster_destruction(): """Tests that returning a pointer to a type that gets converted with a custom type caster gets - destroyed when the function has py::return_value_policy::take_ownership policy applied.""" + destroyed when the function has py::return_value_policy::take_ownership policy applied. + """ cstats = m.destruction_tester_cstats() # This one *doesn't* have take_ownership: the pointer should be used but not destroyed: z = m.custom_caster_no_destroy() - assert cstats.alive() == 1 and cstats.default_constructions == 1 + assert cstats.alive() == 1 + assert cstats.default_constructions == 1 assert z # take_ownership applied: this constructs a new object, casts it, then destroys it: diff --git a/pybind11/tests/test_custom_type_setup.py b/pybind11/tests/test_custom_type_setup.py index 19b44c9de..e63ff5758 100644 --- a/pybind11/tests/test_custom_type_setup.py +++ b/pybind11/tests/test_custom_type_setup.py @@ -7,7 +7,7 @@ import env # noqa: F401 from pybind11_tests import custom_type_setup as m -@pytest.fixture +@pytest.fixture() def gc_tester(): """Tests that an object is garbage collected. diff --git a/pybind11/tests/test_docstring_options.cpp b/pybind11/tests/test_docstring_options.cpp index 4d44f4e20..dda1cf6e4 100644 --- a/pybind11/tests/test_docstring_options.cpp +++ b/pybind11/tests/test_docstring_options.cpp @@ -85,4 +85,57 @@ TEST_SUBMODULE(docstring_options, m) { &DocstringTestFoo::setValue, "This is a property docstring"); } + + { + enum class DocstringTestEnum1 { Member1, Member2 }; + + py::enum_(m, "DocstringTestEnum1", "Enum docstring") + .value("Member1", DocstringTestEnum1::Member1) + .value("Member2", DocstringTestEnum1::Member2); + } + + { + py::options options; + options.enable_enum_members_docstring(); + + enum class DocstringTestEnum2 { Member1, Member2 }; + + py::enum_(m, "DocstringTestEnum2", "Enum docstring") + .value("Member1", DocstringTestEnum2::Member1) + .value("Member2", DocstringTestEnum2::Member2); + } + + { + py::options options; + options.disable_enum_members_docstring(); + + enum class DocstringTestEnum3 { Member1, Member2 }; + + py::enum_(m, "DocstringTestEnum3", "Enum docstring") + .value("Member1", DocstringTestEnum3::Member1) + .value("Member2", DocstringTestEnum3::Member2); + } + + { + py::options options; + options.disable_user_defined_docstrings(); + + enum class DocstringTestEnum4 { Member1, Member2 }; + + py::enum_(m, "DocstringTestEnum4", "Enum docstring") + .value("Member1", DocstringTestEnum4::Member1) + .value("Member2", DocstringTestEnum4::Member2); + } + + { + py::options options; + options.disable_user_defined_docstrings(); + options.disable_enum_members_docstring(); + + enum class DocstringTestEnum5 { Member1, Member2 }; + + py::enum_(m, "DocstringTestEnum5", "Enum docstring") + .value("Member1", DocstringTestEnum5::Member1) + .value("Member2", DocstringTestEnum5::Member2); + } } diff --git a/pybind11/tests/test_docstring_options.py b/pybind11/tests/test_docstring_options.py index fcd16b89f..e6f5a9d98 100644 --- a/pybind11/tests/test_docstring_options.py +++ b/pybind11/tests/test_docstring_options.py @@ -39,3 +39,26 @@ def test_docstring_options(): # Suppression of user-defined docstrings for non-function objects assert not m.DocstringTestFoo.__doc__ assert not m.DocstringTestFoo.value_prop.__doc__ + + # Check existig behaviour of enum docstings + assert ( + m.DocstringTestEnum1.__doc__ + == "Enum docstring\n\nMembers:\n\n Member1\n\n Member2" + ) + + # options.enable_enum_members_docstring() + assert ( + m.DocstringTestEnum2.__doc__ + == "Enum docstring\n\nMembers:\n\n Member1\n\n Member2" + ) + + # options.disable_enum_members_docstring() + assert m.DocstringTestEnum3.__doc__ == "Enum docstring" + + # options.disable_user_defined_docstrings() + assert m.DocstringTestEnum4.__doc__ == "Members:\n\n Member1\n\n Member2" + + # options.disable_user_defined_docstrings() + # options.disable_enum_members_docstring() + # When all options are disabled, no docstring (instead of an empty one) should be generated + assert m.DocstringTestEnum5.__doc__ is None diff --git a/pybind11/tests/test_eigen_matrix.cpp b/pybind11/tests/test_eigen_matrix.cpp new file mode 100644 index 000000000..554cc4d7f --- /dev/null +++ b/pybind11/tests/test_eigen_matrix.cpp @@ -0,0 +1,428 @@ +/* + tests/eigen.cpp -- automatic conversion of Eigen types + + Copyright (c) 2016 Wenzel Jakob + + All rights reserved. Use of this source code is governed by a + BSD-style license that can be found in the LICENSE file. +*/ + +#include +#include + +#include "constructor_stats.h" +#include "pybind11_tests.h" + +PYBIND11_WARNING_DISABLE_MSVC(4996) + +#include + +using MatrixXdR = Eigen::Matrix; + +// Sets/resets a testing reference matrix to have values of 10*r + c, where r and c are the +// (1-based) row/column number. +template +void reset_ref(M &x) { + for (int i = 0; i < x.rows(); i++) { + for (int j = 0; j < x.cols(); j++) { + x(i, j) = 11 + 10 * i + j; + } + } +} + +// Returns a static, column-major matrix +Eigen::MatrixXd &get_cm() { + static Eigen::MatrixXd *x; + if (!x) { + x = new Eigen::MatrixXd(3, 3); + reset_ref(*x); + } + return *x; +} +// Likewise, but row-major +MatrixXdR &get_rm() { + static MatrixXdR *x; + if (!x) { + x = new MatrixXdR(3, 3); + reset_ref(*x); + } + return *x; +} +// Resets the values of the static matrices returned by get_cm()/get_rm() +void reset_refs() { + reset_ref(get_cm()); + reset_ref(get_rm()); +} + +// Returns element 2,1 from a matrix (used to test copy/nocopy) +double get_elem(const Eigen::Ref &m) { return m(2, 1); }; + +// Returns a matrix with 10*r + 100*c added to each matrix element (to help test that the matrix +// reference is referencing rows/columns correctly). +template +Eigen::MatrixXd adjust_matrix(MatrixArgType m) { + Eigen::MatrixXd ret(m); + for (int c = 0; c < m.cols(); c++) { + for (int r = 0; r < m.rows(); r++) { + ret(r, c) += 10 * r + 100 * c; // NOLINT(clang-analyzer-core.uninitialized.Assign) + } + } + return ret; +} + +struct CustomOperatorNew { + CustomOperatorNew() = default; + + Eigen::Matrix4d a = Eigen::Matrix4d::Zero(); + Eigen::Matrix4d b = Eigen::Matrix4d::Identity(); + + EIGEN_MAKE_ALIGNED_OPERATOR_NEW; +}; + +TEST_SUBMODULE(eigen_matrix, m) { + using FixedMatrixR = Eigen::Matrix; + using FixedMatrixC = Eigen::Matrix; + using DenseMatrixR = Eigen::Matrix; + using DenseMatrixC = Eigen::Matrix; + using FourRowMatrixC = Eigen::Matrix; + using FourColMatrixC = Eigen::Matrix; + using FourRowMatrixR = Eigen::Matrix; + using FourColMatrixR = Eigen::Matrix; + using SparseMatrixR = Eigen::SparseMatrix; + using SparseMatrixC = Eigen::SparseMatrix; + + // various tests + m.def("double_col", [](const Eigen::VectorXf &x) -> Eigen::VectorXf { return 2.0f * x; }); + m.def("double_row", + [](const Eigen::RowVectorXf &x) -> Eigen::RowVectorXf { return 2.0f * x; }); + m.def("double_complex", + [](const Eigen::VectorXcf &x) -> Eigen::VectorXcf { return 2.0f * x; }); + m.def("double_threec", [](py::EigenDRef x) { x *= 2; }); + m.def("double_threer", [](py::EigenDRef x) { x *= 2; }); + m.def("double_mat_cm", [](const Eigen::MatrixXf &x) -> Eigen::MatrixXf { return 2.0f * x; }); + m.def("double_mat_rm", [](const DenseMatrixR &x) -> DenseMatrixR { return 2.0f * x; }); + + // test_eigen_ref_to_python + // Different ways of passing via Eigen::Ref; the first and second are the Eigen-recommended + m.def("cholesky1", + [](const Eigen::Ref &x) -> Eigen::MatrixXd { return x.llt().matrixL(); }); + m.def("cholesky2", [](const Eigen::Ref &x) -> Eigen::MatrixXd { + return x.llt().matrixL(); + }); + m.def("cholesky3", + [](const Eigen::Ref &x) -> Eigen::MatrixXd { return x.llt().matrixL(); }); + m.def("cholesky4", [](const Eigen::Ref &x) -> Eigen::MatrixXd { + return x.llt().matrixL(); + }); + + // test_eigen_ref_mutators + // Mutators: these add some value to the given element using Eigen, but Eigen should be mapping + // into the numpy array data and so the result should show up there. There are three versions: + // one that works on a contiguous-row matrix (numpy's default), one for a contiguous-column + // matrix, and one for any matrix. + auto add_rm = [](Eigen::Ref x, int r, int c, double v) { x(r, c) += v; }; + auto add_cm = [](Eigen::Ref x, int r, int c, double v) { x(r, c) += v; }; + + // Mutators (Eigen maps into numpy variables): + m.def("add_rm", add_rm); // Only takes row-contiguous + m.def("add_cm", add_cm); // Only takes column-contiguous + // Overloaded versions that will accept either row or column contiguous: + m.def("add1", add_rm); + m.def("add1", add_cm); + m.def("add2", add_cm); + m.def("add2", add_rm); + // This one accepts a matrix of any stride: + m.def("add_any", + [](py::EigenDRef x, int r, int c, double v) { x(r, c) += v; }); + + // Return mutable references (numpy maps into eigen variables) + m.def("get_cm_ref", []() { return Eigen::Ref(get_cm()); }); + m.def("get_rm_ref", []() { return Eigen::Ref(get_rm()); }); + // The same references, but non-mutable (numpy maps into eigen variables, but is !writeable) + m.def("get_cm_const_ref", []() { return Eigen::Ref(get_cm()); }); + m.def("get_rm_const_ref", []() { return Eigen::Ref(get_rm()); }); + + m.def("reset_refs", reset_refs); // Restores get_{cm,rm}_ref to original values + + // Increments and returns ref to (same) matrix + m.def( + "incr_matrix", + [](Eigen::Ref m, double v) { + m += Eigen::MatrixXd::Constant(m.rows(), m.cols(), v); + return m; + }, + py::return_value_policy::reference); + + // Same, but accepts a matrix of any strides + m.def( + "incr_matrix_any", + [](py::EigenDRef m, double v) { + m += Eigen::MatrixXd::Constant(m.rows(), m.cols(), v); + return m; + }, + py::return_value_policy::reference); + + // Returns an eigen slice of even rows + m.def( + "even_rows", + [](py::EigenDRef m) { + return py::EigenDMap( + m.data(), + (m.rows() + 1) / 2, + m.cols(), + py::EigenDStride(m.outerStride(), 2 * m.innerStride())); + }, + py::return_value_policy::reference); + + // Returns an eigen slice of even columns + m.def( + "even_cols", + [](py::EigenDRef m) { + return py::EigenDMap( + m.data(), + m.rows(), + (m.cols() + 1) / 2, + py::EigenDStride(2 * m.outerStride(), m.innerStride())); + }, + py::return_value_policy::reference); + + // Returns diagonals: a vector-like object with an inner stride != 1 + m.def("diagonal", [](const Eigen::Ref &x) { return x.diagonal(); }); + m.def("diagonal_1", + [](const Eigen::Ref &x) { return x.diagonal<1>(); }); + m.def("diagonal_n", + [](const Eigen::Ref &x, int index) { return x.diagonal(index); }); + + // Return a block of a matrix (gives non-standard strides) + m.def("block", + [m](const py::object &x_obj, + int start_row, + int start_col, + int block_rows, + int block_cols) { + return m.attr("_block")(x_obj, x_obj, start_row, start_col, block_rows, block_cols); + }); + + m.def( + "_block", + [](const py::object &x_obj, + const Eigen::Ref &x, + int start_row, + int start_col, + int block_rows, + int block_cols) { + // See PR #4217 for background. This test is a bit over the top, but might be useful + // as a concrete example to point to when explaining the dangling reference trap. + auto i0 = py::make_tuple(0, 0); + auto x0_orig = x_obj[*i0].cast(); + if (x(0, 0) != x0_orig) { + throw std::runtime_error( + "Something in the type_caster for Eigen::Ref is terribly wrong."); + } + double x0_mod = x0_orig + 1; + x_obj[*i0] = x0_mod; + auto copy_detected = (x(0, 0) != x0_mod); + x_obj[*i0] = x0_orig; + if (copy_detected) { + throw std::runtime_error("type_caster for Eigen::Ref made a copy."); + } + return x.block(start_row, start_col, block_rows, block_cols); + }, + py::keep_alive<0, 1>()); + + // test_eigen_return_references, test_eigen_keepalive + // return value referencing/copying tests: + class ReturnTester { + Eigen::MatrixXd mat = create(); + + public: + ReturnTester() { print_created(this); } + ~ReturnTester() { print_destroyed(this); } + static Eigen::MatrixXd create() { return Eigen::MatrixXd::Ones(10, 10); } + // NOLINTNEXTLINE(readability-const-return-type) + static const Eigen::MatrixXd createConst() { return Eigen::MatrixXd::Ones(10, 10); } + Eigen::MatrixXd &get() { return mat; } + Eigen::MatrixXd *getPtr() { return &mat; } + const Eigen::MatrixXd &view() { return mat; } + const Eigen::MatrixXd *viewPtr() { return &mat; } + Eigen::Ref ref() { return mat; } + Eigen::Ref refConst() { return mat; } + Eigen::Block block(int r, int c, int nrow, int ncol) { + return mat.block(r, c, nrow, ncol); + } + Eigen::Block blockConst(int r, int c, int nrow, int ncol) const { + return mat.block(r, c, nrow, ncol); + } + py::EigenDMap corners() { + return py::EigenDMap( + mat.data(), + py::EigenDStride(mat.outerStride() * (mat.outerSize() - 1), + mat.innerStride() * (mat.innerSize() - 1))); + } + py::EigenDMap cornersConst() const { + return py::EigenDMap( + mat.data(), + py::EigenDStride(mat.outerStride() * (mat.outerSize() - 1), + mat.innerStride() * (mat.innerSize() - 1))); + } + }; + using rvp = py::return_value_policy; + py::class_(m, "ReturnTester") + .def(py::init<>()) + .def_static("create", &ReturnTester::create) + .def_static("create_const", &ReturnTester::createConst) + .def("get", &ReturnTester::get, rvp::reference_internal) + .def("get_ptr", &ReturnTester::getPtr, rvp::reference_internal) + .def("view", &ReturnTester::view, rvp::reference_internal) + .def("view_ptr", &ReturnTester::view, rvp::reference_internal) + .def("copy_get", &ReturnTester::get) // Default rvp: copy + .def("copy_view", &ReturnTester::view) // " + .def("ref", &ReturnTester::ref) // Default for Ref is to reference + .def("ref_const", &ReturnTester::refConst) // Likewise, but const + .def("ref_safe", &ReturnTester::ref, rvp::reference_internal) + .def("ref_const_safe", &ReturnTester::refConst, rvp::reference_internal) + .def("copy_ref", &ReturnTester::ref, rvp::copy) + .def("copy_ref_const", &ReturnTester::refConst, rvp::copy) + .def("block", &ReturnTester::block) + .def("block_safe", &ReturnTester::block, rvp::reference_internal) + .def("block_const", &ReturnTester::blockConst, rvp::reference_internal) + .def("copy_block", &ReturnTester::block, rvp::copy) + .def("corners", &ReturnTester::corners, rvp::reference_internal) + .def("corners_const", &ReturnTester::cornersConst, rvp::reference_internal); + + // test_special_matrix_objects + // Returns a DiagonalMatrix with diagonal (1,2,3,...) + m.def("incr_diag", [](int k) { + Eigen::DiagonalMatrix m(k); + for (int i = 0; i < k; i++) { + m.diagonal()[i] = i + 1; + } + return m; + }); + + // Returns a SelfAdjointView referencing the lower triangle of m + m.def("symmetric_lower", + [](const Eigen::MatrixXi &m) { return m.selfadjointView(); }); + // Returns a SelfAdjointView referencing the lower triangle of m + m.def("symmetric_upper", + [](const Eigen::MatrixXi &m) { return m.selfadjointView(); }); + + // Test matrix for various functions below. + Eigen::MatrixXf mat(5, 6); + mat << 0, 3, 0, 0, 0, 11, 22, 0, 0, 0, 17, 11, 7, 5, 0, 1, 0, 11, 0, 0, 0, 0, 0, 11, 0, 0, 14, + 0, 8, 11; + + // test_fixed, and various other tests + m.def("fixed_r", [mat]() -> FixedMatrixR { return FixedMatrixR(mat); }); + // Our Eigen does a hack which respects constness through the numpy writeable flag. + // Therefore, the const return actually affects this type despite being an rvalue. + // NOLINTNEXTLINE(readability-const-return-type) + m.def("fixed_r_const", [mat]() -> const FixedMatrixR { return FixedMatrixR(mat); }); + m.def("fixed_c", [mat]() -> FixedMatrixC { return FixedMatrixC(mat); }); + m.def("fixed_copy_r", [](const FixedMatrixR &m) -> FixedMatrixR { return m; }); + m.def("fixed_copy_c", [](const FixedMatrixC &m) -> FixedMatrixC { return m; }); + // test_mutator_descriptors + m.def("fixed_mutator_r", [](const Eigen::Ref &) {}); + m.def("fixed_mutator_c", [](const Eigen::Ref &) {}); + m.def("fixed_mutator_a", [](const py::EigenDRef &) {}); + // test_dense + m.def("dense_r", [mat]() -> DenseMatrixR { return DenseMatrixR(mat); }); + m.def("dense_c", [mat]() -> DenseMatrixC { return DenseMatrixC(mat); }); + m.def("dense_copy_r", [](const DenseMatrixR &m) -> DenseMatrixR { return m; }); + m.def("dense_copy_c", [](const DenseMatrixC &m) -> DenseMatrixC { return m; }); + // test_sparse, test_sparse_signature + m.def("sparse_r", [mat]() -> SparseMatrixR { + // NOLINTNEXTLINE(clang-analyzer-core.uninitialized.UndefReturn) + return Eigen::SparseView(mat); + }); + m.def("sparse_c", + [mat]() -> SparseMatrixC { return Eigen::SparseView(mat); }); + m.def("sparse_copy_r", [](const SparseMatrixR &m) -> SparseMatrixR { return m; }); + m.def("sparse_copy_c", [](const SparseMatrixC &m) -> SparseMatrixC { return m; }); + // test_partially_fixed + m.def("partial_copy_four_rm_r", [](const FourRowMatrixR &m) -> FourRowMatrixR { return m; }); + m.def("partial_copy_four_rm_c", [](const FourColMatrixR &m) -> FourColMatrixR { return m; }); + m.def("partial_copy_four_cm_r", [](const FourRowMatrixC &m) -> FourRowMatrixC { return m; }); + m.def("partial_copy_four_cm_c", [](const FourColMatrixC &m) -> FourColMatrixC { return m; }); + + // test_cpp_casting + // Test that we can cast a numpy object to a Eigen::MatrixXd explicitly + m.def("cpp_copy", [](py::handle m) { return m.cast()(1, 0); }); + m.def("cpp_ref_c", [](py::handle m) { return m.cast>()(1, 0); }); + m.def("cpp_ref_r", [](py::handle m) { return m.cast>()(1, 0); }); + m.def("cpp_ref_any", + [](py::handle m) { return m.cast>()(1, 0); }); + + // [workaround(intel)] ICC 20/21 breaks with py::arg().stuff, using py::arg{}.stuff works. + + // test_nocopy_wrapper + // Test that we can prevent copying into an argument that would normally copy: First a version + // that would allow copying (if types or strides don't match) for comparison: + m.def("get_elem", &get_elem); + // Now this alternative that calls the tells pybind to fail rather than copy: + m.def( + "get_elem_nocopy", + [](const Eigen::Ref &m) -> double { return get_elem(m); }, + py::arg{}.noconvert()); + // Also test a row-major-only no-copy const ref: + m.def( + "get_elem_rm_nocopy", + [](Eigen::Ref> &m) -> long { + return m(2, 1); + }, + py::arg{}.noconvert()); + + // test_issue738, test_zero_length + // Issue #738: 1×N or N×1 2D matrices were neither accepted nor properly copied with an + // incompatible stride value on the length-1 dimension--but that should be allowed (without + // requiring a copy!) because the stride value can be safely ignored on a size-1 dimension. + // Similarly, 0×N or N×0 matrices were not accepted--again, these should be allowed since + // they contain no data. This particularly affects numpy ≥ 1.23, which sets the strides to + // 0 if any dimension size is 0. + m.def("iss738_f1", + &adjust_matrix &>, + py::arg{}.noconvert()); + m.def("iss738_f2", + &adjust_matrix> &>, + py::arg{}.noconvert()); + + // test_issue1105 + // Issue #1105: when converting from a numpy two-dimensional (Nx1) or (1xN) value into a dense + // eigen Vector or RowVector, the argument would fail to load because the numpy copy would + // fail: numpy won't broadcast a Nx1 into a 1-dimensional vector. + m.def("iss1105_col", [](const Eigen::VectorXd &) { return true; }); + m.def("iss1105_row", [](const Eigen::RowVectorXd &) { return true; }); + + // test_named_arguments + // Make sure named arguments are working properly: + m.def( + "matrix_multiply", + [](const py::EigenDRef &A, + const py::EigenDRef &B) -> Eigen::MatrixXd { + if (A.cols() != B.rows()) { + throw std::domain_error("Nonconformable matrices!"); + } + return A * B; + }, + py::arg("A"), + py::arg("B")); + + // test_custom_operator_new + py::class_(m, "CustomOperatorNew") + .def(py::init<>()) + .def_readonly("a", &CustomOperatorNew::a) + .def_readonly("b", &CustomOperatorNew::b); + + // test_eigen_ref_life_support + // In case of a failure (the caster's temp array does not live long enough), creating + // a new array (np.ones(10)) increases the chances that the temp array will be garbage + // collected and/or that its memory will be overridden with different values. + m.def("get_elem_direct", [](const Eigen::Ref &v) { + py::module_::import("numpy").attr("ones")(10); + return v(5); + }); + m.def("get_elem_indirect", [](std::vector> v) { + py::module_::import("numpy").attr("ones")(10); + return v[0](5); + }); +} diff --git a/pybind11/tests/test_eigen_matrix.py b/pybind11/tests/test_eigen_matrix.py new file mode 100644 index 000000000..b2e76740b --- /dev/null +++ b/pybind11/tests/test_eigen_matrix.py @@ -0,0 +1,807 @@ +import pytest + +from pybind11_tests import ConstructorStats + +np = pytest.importorskip("numpy") +m = pytest.importorskip("pybind11_tests.eigen_matrix") + + +ref = np.array( + [ + [0.0, 3, 0, 0, 0, 11], + [22, 0, 0, 0, 17, 11], + [7, 5, 0, 1, 0, 11], + [0, 0, 0, 0, 0, 11], + [0, 0, 14, 0, 8, 11], + ] +) + + +def assert_equal_ref(mat): + np.testing.assert_array_equal(mat, ref) + + +def assert_sparse_equal_ref(sparse_mat): + assert_equal_ref(sparse_mat.toarray()) + + +def test_fixed(): + assert_equal_ref(m.fixed_c()) + assert_equal_ref(m.fixed_r()) + assert_equal_ref(m.fixed_copy_r(m.fixed_r())) + assert_equal_ref(m.fixed_copy_c(m.fixed_c())) + assert_equal_ref(m.fixed_copy_r(m.fixed_c())) + assert_equal_ref(m.fixed_copy_c(m.fixed_r())) + + +def test_dense(): + assert_equal_ref(m.dense_r()) + assert_equal_ref(m.dense_c()) + assert_equal_ref(m.dense_copy_r(m.dense_r())) + assert_equal_ref(m.dense_copy_c(m.dense_c())) + assert_equal_ref(m.dense_copy_r(m.dense_c())) + assert_equal_ref(m.dense_copy_c(m.dense_r())) + + +def test_partially_fixed(): + ref2 = np.array([[0.0, 1, 2, 3], [4, 5, 6, 7], [8, 9, 10, 11], [12, 13, 14, 15]]) + np.testing.assert_array_equal(m.partial_copy_four_rm_r(ref2), ref2) + np.testing.assert_array_equal(m.partial_copy_four_rm_c(ref2), ref2) + np.testing.assert_array_equal(m.partial_copy_four_rm_r(ref2[:, 1]), ref2[:, [1]]) + np.testing.assert_array_equal(m.partial_copy_four_rm_c(ref2[0, :]), ref2[[0], :]) + np.testing.assert_array_equal( + m.partial_copy_four_rm_r(ref2[:, (0, 2)]), ref2[:, (0, 2)] + ) + np.testing.assert_array_equal( + m.partial_copy_four_rm_c(ref2[(3, 1, 2), :]), ref2[(3, 1, 2), :] + ) + + np.testing.assert_array_equal(m.partial_copy_four_cm_r(ref2), ref2) + np.testing.assert_array_equal(m.partial_copy_four_cm_c(ref2), ref2) + np.testing.assert_array_equal(m.partial_copy_four_cm_r(ref2[:, 1]), ref2[:, [1]]) + np.testing.assert_array_equal(m.partial_copy_four_cm_c(ref2[0, :]), ref2[[0], :]) + np.testing.assert_array_equal( + m.partial_copy_four_cm_r(ref2[:, (0, 2)]), ref2[:, (0, 2)] + ) + np.testing.assert_array_equal( + m.partial_copy_four_cm_c(ref2[(3, 1, 2), :]), ref2[(3, 1, 2), :] + ) + + # TypeError should be raise for a shape mismatch + functions = [ + m.partial_copy_four_rm_r, + m.partial_copy_four_rm_c, + m.partial_copy_four_cm_r, + m.partial_copy_four_cm_c, + ] + matrix_with_wrong_shape = [[1, 2], [3, 4]] + for f in functions: + with pytest.raises(TypeError) as excinfo: + f(matrix_with_wrong_shape) + assert "incompatible function arguments" in str(excinfo.value) + + +def test_mutator_descriptors(): + zr = np.arange(30, dtype="float32").reshape(5, 6) # row-major + zc = zr.reshape(6, 5).transpose() # column-major + + m.fixed_mutator_r(zr) + m.fixed_mutator_c(zc) + m.fixed_mutator_a(zr) + m.fixed_mutator_a(zc) + with pytest.raises(TypeError) as excinfo: + m.fixed_mutator_r(zc) + assert ( + "(arg0: numpy.ndarray[numpy.float32[5, 6]," + " flags.writeable, flags.c_contiguous]) -> None" in str(excinfo.value) + ) + with pytest.raises(TypeError) as excinfo: + m.fixed_mutator_c(zr) + assert ( + "(arg0: numpy.ndarray[numpy.float32[5, 6]," + " flags.writeable, flags.f_contiguous]) -> None" in str(excinfo.value) + ) + with pytest.raises(TypeError) as excinfo: + m.fixed_mutator_a(np.array([[1, 2], [3, 4]], dtype="float32")) + assert "(arg0: numpy.ndarray[numpy.float32[5, 6], flags.writeable]) -> None" in str( + excinfo.value + ) + zr.flags.writeable = False + with pytest.raises(TypeError): + m.fixed_mutator_r(zr) + with pytest.raises(TypeError): + m.fixed_mutator_a(zr) + + +def test_cpp_casting(): + assert m.cpp_copy(m.fixed_r()) == 22.0 + assert m.cpp_copy(m.fixed_c()) == 22.0 + z = np.array([[5.0, 6], [7, 8]]) + assert m.cpp_copy(z) == 7.0 + assert m.cpp_copy(m.get_cm_ref()) == 21.0 + assert m.cpp_copy(m.get_rm_ref()) == 21.0 + assert m.cpp_ref_c(m.get_cm_ref()) == 21.0 + assert m.cpp_ref_r(m.get_rm_ref()) == 21.0 + with pytest.raises(RuntimeError) as excinfo: + # Can't reference m.fixed_c: it contains floats, m.cpp_ref_any wants doubles + m.cpp_ref_any(m.fixed_c()) + assert "Unable to cast Python instance" in str(excinfo.value) + with pytest.raises(RuntimeError) as excinfo: + # Can't reference m.fixed_r: it contains floats, m.cpp_ref_any wants doubles + m.cpp_ref_any(m.fixed_r()) + assert "Unable to cast Python instance" in str(excinfo.value) + assert m.cpp_ref_any(m.ReturnTester.create()) == 1.0 + + assert m.cpp_ref_any(m.get_cm_ref()) == 21.0 + assert m.cpp_ref_any(m.get_cm_ref()) == 21.0 + + +def test_pass_readonly_array(): + z = np.full((5, 6), 42.0) + z.flags.writeable = False + np.testing.assert_array_equal(z, m.fixed_copy_r(z)) + np.testing.assert_array_equal(m.fixed_r_const(), m.fixed_r()) + assert not m.fixed_r_const().flags.writeable + np.testing.assert_array_equal(m.fixed_copy_r(m.fixed_r_const()), m.fixed_r_const()) + + +def test_nonunit_stride_from_python(): + counting_mat = np.arange(9.0, dtype=np.float32).reshape((3, 3)) + second_row = counting_mat[1, :] + second_col = counting_mat[:, 1] + np.testing.assert_array_equal(m.double_row(second_row), 2.0 * second_row) + np.testing.assert_array_equal(m.double_col(second_row), 2.0 * second_row) + np.testing.assert_array_equal(m.double_complex(second_row), 2.0 * second_row) + np.testing.assert_array_equal(m.double_row(second_col), 2.0 * second_col) + np.testing.assert_array_equal(m.double_col(second_col), 2.0 * second_col) + np.testing.assert_array_equal(m.double_complex(second_col), 2.0 * second_col) + + counting_3d = np.arange(27.0, dtype=np.float32).reshape((3, 3, 3)) + slices = [counting_3d[0, :, :], counting_3d[:, 0, :], counting_3d[:, :, 0]] + for ref_mat in slices: + np.testing.assert_array_equal(m.double_mat_cm(ref_mat), 2.0 * ref_mat) + np.testing.assert_array_equal(m.double_mat_rm(ref_mat), 2.0 * ref_mat) + + # Mutator: + m.double_threer(second_row) + m.double_threec(second_col) + np.testing.assert_array_equal(counting_mat, [[0.0, 2, 2], [6, 16, 10], [6, 14, 8]]) + + +def test_negative_stride_from_python(msg): + """Eigen doesn't support (as of yet) negative strides. When a function takes an Eigen matrix by + copy or const reference, we can pass a numpy array that has negative strides. Otherwise, an + exception will be thrown as Eigen will not be able to map the numpy array.""" + + counting_mat = np.arange(9.0, dtype=np.float32).reshape((3, 3)) + counting_mat = counting_mat[::-1, ::-1] + second_row = counting_mat[1, :] + second_col = counting_mat[:, 1] + np.testing.assert_array_equal(m.double_row(second_row), 2.0 * second_row) + np.testing.assert_array_equal(m.double_col(second_row), 2.0 * second_row) + np.testing.assert_array_equal(m.double_complex(second_row), 2.0 * second_row) + np.testing.assert_array_equal(m.double_row(second_col), 2.0 * second_col) + np.testing.assert_array_equal(m.double_col(second_col), 2.0 * second_col) + np.testing.assert_array_equal(m.double_complex(second_col), 2.0 * second_col) + + counting_3d = np.arange(27.0, dtype=np.float32).reshape((3, 3, 3)) + counting_3d = counting_3d[::-1, ::-1, ::-1] + slices = [counting_3d[0, :, :], counting_3d[:, 0, :], counting_3d[:, :, 0]] + for ref_mat in slices: + np.testing.assert_array_equal(m.double_mat_cm(ref_mat), 2.0 * ref_mat) + np.testing.assert_array_equal(m.double_mat_rm(ref_mat), 2.0 * ref_mat) + + # Mutator: + with pytest.raises(TypeError) as excinfo: + m.double_threer(second_row) + assert ( + msg(excinfo.value) + == """ + double_threer(): incompatible function arguments. The following argument types are supported: + 1. (arg0: numpy.ndarray[numpy.float32[1, 3], flags.writeable]) -> None + + Invoked with: """ + + repr(np.array([5.0, 4.0, 3.0], dtype="float32")) + ) + + with pytest.raises(TypeError) as excinfo: + m.double_threec(second_col) + assert ( + msg(excinfo.value) + == """ + double_threec(): incompatible function arguments. The following argument types are supported: + 1. (arg0: numpy.ndarray[numpy.float32[3, 1], flags.writeable]) -> None + + Invoked with: """ + + repr(np.array([7.0, 4.0, 1.0], dtype="float32")) + ) + + +def test_block_runtime_error_type_caster_eigen_ref_made_a_copy(): + with pytest.raises(RuntimeError) as excinfo: + m.block(ref, 0, 0, 0, 0) + assert str(excinfo.value) == "type_caster for Eigen::Ref made a copy." + + +def test_nonunit_stride_to_python(): + assert np.all(m.diagonal(ref) == ref.diagonal()) + assert np.all(m.diagonal_1(ref) == ref.diagonal(1)) + for i in range(-5, 7): + assert np.all(m.diagonal_n(ref, i) == ref.diagonal(i)), f"m.diagonal_n({i})" + + # Must be order="F", otherwise the type_caster will make a copy and + # m.block() will return a dangling reference (heap-use-after-free). + rof = np.asarray(ref, order="F") + assert np.all(m.block(rof, 2, 1, 3, 3) == rof[2:5, 1:4]) + assert np.all(m.block(rof, 1, 4, 4, 2) == rof[1:, 4:]) + assert np.all(m.block(rof, 1, 4, 3, 2) == rof[1:4, 4:]) + + +def test_eigen_ref_to_python(): + chols = [m.cholesky1, m.cholesky2, m.cholesky3, m.cholesky4] + for i, chol in enumerate(chols, start=1): + mymat = chol(np.array([[1.0, 2, 4], [2, 13, 23], [4, 23, 77]])) + assert np.all( + mymat == np.array([[1, 0, 0], [2, 3, 0], [4, 5, 6]]) + ), f"cholesky{i}" + + +def assign_both(a1, a2, r, c, v): + a1[r, c] = v + a2[r, c] = v + + +def array_copy_but_one(a, r, c, v): + z = np.array(a, copy=True) + z[r, c] = v + return z + + +def test_eigen_return_references(): + """Tests various ways of returning references and non-referencing copies""" + + primary = np.ones((10, 10)) + a = m.ReturnTester() + a_get1 = a.get() + assert not a_get1.flags.owndata + assert a_get1.flags.writeable + assign_both(a_get1, primary, 3, 3, 5) + a_get2 = a.get_ptr() + assert not a_get2.flags.owndata + assert a_get2.flags.writeable + assign_both(a_get1, primary, 2, 3, 6) + + a_view1 = a.view() + assert not a_view1.flags.owndata + assert not a_view1.flags.writeable + with pytest.raises(ValueError): + a_view1[2, 3] = 4 + a_view2 = a.view_ptr() + assert not a_view2.flags.owndata + assert not a_view2.flags.writeable + with pytest.raises(ValueError): + a_view2[2, 3] = 4 + + a_copy1 = a.copy_get() + assert a_copy1.flags.owndata + assert a_copy1.flags.writeable + np.testing.assert_array_equal(a_copy1, primary) + a_copy1[7, 7] = -44 # Shouldn't affect anything else + c1want = array_copy_but_one(primary, 7, 7, -44) + a_copy2 = a.copy_view() + assert a_copy2.flags.owndata + assert a_copy2.flags.writeable + np.testing.assert_array_equal(a_copy2, primary) + a_copy2[4, 4] = -22 # Shouldn't affect anything else + c2want = array_copy_but_one(primary, 4, 4, -22) + + a_ref1 = a.ref() + assert not a_ref1.flags.owndata + assert a_ref1.flags.writeable + assign_both(a_ref1, primary, 1, 1, 15) + a_ref2 = a.ref_const() + assert not a_ref2.flags.owndata + assert not a_ref2.flags.writeable + with pytest.raises(ValueError): + a_ref2[5, 5] = 33 + a_ref3 = a.ref_safe() + assert not a_ref3.flags.owndata + assert a_ref3.flags.writeable + assign_both(a_ref3, primary, 0, 7, 99) + a_ref4 = a.ref_const_safe() + assert not a_ref4.flags.owndata + assert not a_ref4.flags.writeable + with pytest.raises(ValueError): + a_ref4[7, 0] = 987654321 + + a_copy3 = a.copy_ref() + assert a_copy3.flags.owndata + assert a_copy3.flags.writeable + np.testing.assert_array_equal(a_copy3, primary) + a_copy3[8, 1] = 11 + c3want = array_copy_but_one(primary, 8, 1, 11) + a_copy4 = a.copy_ref_const() + assert a_copy4.flags.owndata + assert a_copy4.flags.writeable + np.testing.assert_array_equal(a_copy4, primary) + a_copy4[8, 4] = 88 + c4want = array_copy_but_one(primary, 8, 4, 88) + + a_block1 = a.block(3, 3, 2, 2) + assert not a_block1.flags.owndata + assert a_block1.flags.writeable + a_block1[0, 0] = 55 + primary[3, 3] = 55 + a_block2 = a.block_safe(2, 2, 3, 2) + assert not a_block2.flags.owndata + assert a_block2.flags.writeable + a_block2[2, 1] = -123 + primary[4, 3] = -123 + a_block3 = a.block_const(6, 7, 4, 3) + assert not a_block3.flags.owndata + assert not a_block3.flags.writeable + with pytest.raises(ValueError): + a_block3[2, 2] = -44444 + + a_copy5 = a.copy_block(2, 2, 2, 3) + assert a_copy5.flags.owndata + assert a_copy5.flags.writeable + np.testing.assert_array_equal(a_copy5, primary[2:4, 2:5]) + a_copy5[1, 1] = 777 + c5want = array_copy_but_one(primary[2:4, 2:5], 1, 1, 777) + + a_corn1 = a.corners() + assert not a_corn1.flags.owndata + assert a_corn1.flags.writeable + a_corn1 *= 50 + a_corn1[1, 1] = 999 + primary[0, 0] = 50 + primary[0, 9] = 50 + primary[9, 0] = 50 + primary[9, 9] = 999 + a_corn2 = a.corners_const() + assert not a_corn2.flags.owndata + assert not a_corn2.flags.writeable + with pytest.raises(ValueError): + a_corn2[1, 0] = 51 + + # All of the changes made all the way along should be visible everywhere + # now (except for the copies, of course) + np.testing.assert_array_equal(a_get1, primary) + np.testing.assert_array_equal(a_get2, primary) + np.testing.assert_array_equal(a_view1, primary) + np.testing.assert_array_equal(a_view2, primary) + np.testing.assert_array_equal(a_ref1, primary) + np.testing.assert_array_equal(a_ref2, primary) + np.testing.assert_array_equal(a_ref3, primary) + np.testing.assert_array_equal(a_ref4, primary) + np.testing.assert_array_equal(a_block1, primary[3:5, 3:5]) + np.testing.assert_array_equal(a_block2, primary[2:5, 2:4]) + np.testing.assert_array_equal(a_block3, primary[6:10, 7:10]) + np.testing.assert_array_equal( + a_corn1, primary[0 :: primary.shape[0] - 1, 0 :: primary.shape[1] - 1] + ) + np.testing.assert_array_equal( + a_corn2, primary[0 :: primary.shape[0] - 1, 0 :: primary.shape[1] - 1] + ) + + np.testing.assert_array_equal(a_copy1, c1want) + np.testing.assert_array_equal(a_copy2, c2want) + np.testing.assert_array_equal(a_copy3, c3want) + np.testing.assert_array_equal(a_copy4, c4want) + np.testing.assert_array_equal(a_copy5, c5want) + + +def assert_keeps_alive(cl, method, *args): + cstats = ConstructorStats.get(cl) + start_with = cstats.alive() + a = cl() + assert cstats.alive() == start_with + 1 + z = method(a, *args) + assert cstats.alive() == start_with + 1 + del a + # Here's the keep alive in action: + assert cstats.alive() == start_with + 1 + del z + # Keep alive should have expired: + assert cstats.alive() == start_with + + +def test_eigen_keepalive(): + a = m.ReturnTester() + cstats = ConstructorStats.get(m.ReturnTester) + assert cstats.alive() == 1 + unsafe = [a.ref(), a.ref_const(), a.block(1, 2, 3, 4)] + copies = [ + a.copy_get(), + a.copy_view(), + a.copy_ref(), + a.copy_ref_const(), + a.copy_block(4, 3, 2, 1), + ] + del a + assert cstats.alive() == 0 + del unsafe + del copies + + for meth in [ + m.ReturnTester.get, + m.ReturnTester.get_ptr, + m.ReturnTester.view, + m.ReturnTester.view_ptr, + m.ReturnTester.ref_safe, + m.ReturnTester.ref_const_safe, + m.ReturnTester.corners, + m.ReturnTester.corners_const, + ]: + assert_keeps_alive(m.ReturnTester, meth) + + for meth in [m.ReturnTester.block_safe, m.ReturnTester.block_const]: + assert_keeps_alive(m.ReturnTester, meth, 4, 3, 2, 1) + + +def test_eigen_ref_mutators(): + """Tests Eigen's ability to mutate numpy values""" + + orig = np.array([[1.0, 2, 3], [4, 5, 6], [7, 8, 9]]) + zr = np.array(orig) + zc = np.array(orig, order="F") + m.add_rm(zr, 1, 0, 100) + assert np.all(zr == np.array([[1.0, 2, 3], [104, 5, 6], [7, 8, 9]])) + m.add_cm(zc, 1, 0, 200) + assert np.all(zc == np.array([[1.0, 2, 3], [204, 5, 6], [7, 8, 9]])) + + m.add_any(zr, 1, 0, 20) + assert np.all(zr == np.array([[1.0, 2, 3], [124, 5, 6], [7, 8, 9]])) + m.add_any(zc, 1, 0, 10) + assert np.all(zc == np.array([[1.0, 2, 3], [214, 5, 6], [7, 8, 9]])) + + # Can't reference a col-major array with a row-major Ref, and vice versa: + with pytest.raises(TypeError): + m.add_rm(zc, 1, 0, 1) + with pytest.raises(TypeError): + m.add_cm(zr, 1, 0, 1) + + # Overloads: + m.add1(zr, 1, 0, -100) + m.add2(zr, 1, 0, -20) + assert np.all(zr == orig) + m.add1(zc, 1, 0, -200) + m.add2(zc, 1, 0, -10) + assert np.all(zc == orig) + + # a non-contiguous slice (this won't work on either the row- or + # column-contiguous refs, but should work for the any) + cornersr = zr[0::2, 0::2] + cornersc = zc[0::2, 0::2] + + assert np.all(cornersr == np.array([[1.0, 3], [7, 9]])) + assert np.all(cornersc == np.array([[1.0, 3], [7, 9]])) + + with pytest.raises(TypeError): + m.add_rm(cornersr, 0, 1, 25) + with pytest.raises(TypeError): + m.add_cm(cornersr, 0, 1, 25) + with pytest.raises(TypeError): + m.add_rm(cornersc, 0, 1, 25) + with pytest.raises(TypeError): + m.add_cm(cornersc, 0, 1, 25) + m.add_any(cornersr, 0, 1, 25) + m.add_any(cornersc, 0, 1, 44) + assert np.all(zr == np.array([[1.0, 2, 28], [4, 5, 6], [7, 8, 9]])) + assert np.all(zc == np.array([[1.0, 2, 47], [4, 5, 6], [7, 8, 9]])) + + # You shouldn't be allowed to pass a non-writeable array to a mutating Eigen method: + zro = zr[0:4, 0:4] + zro.flags.writeable = False + with pytest.raises(TypeError): + m.add_rm(zro, 0, 0, 0) + with pytest.raises(TypeError): + m.add_any(zro, 0, 0, 0) + with pytest.raises(TypeError): + m.add1(zro, 0, 0, 0) + with pytest.raises(TypeError): + m.add2(zro, 0, 0, 0) + + # integer array shouldn't be passable to a double-matrix-accepting mutating func: + zi = np.array([[1, 2], [3, 4]]) + with pytest.raises(TypeError): + m.add_rm(zi) + + +def test_numpy_ref_mutators(): + """Tests numpy mutating Eigen matrices (for returned Eigen::Ref<...>s)""" + + m.reset_refs() # In case another test already changed it + + zc = m.get_cm_ref() + zcro = m.get_cm_const_ref() + zr = m.get_rm_ref() + zrro = m.get_rm_const_ref() + + assert [zc[1, 2], zcro[1, 2], zr[1, 2], zrro[1, 2]] == [23] * 4 + + assert not zc.flags.owndata + assert zc.flags.writeable + assert not zr.flags.owndata + assert zr.flags.writeable + assert not zcro.flags.owndata + assert not zcro.flags.writeable + assert not zrro.flags.owndata + assert not zrro.flags.writeable + + zc[1, 2] = 99 + expect = np.array([[11.0, 12, 13], [21, 22, 99], [31, 32, 33]]) + # We should have just changed zc, of course, but also zcro and the original eigen matrix + assert np.all(zc == expect) + assert np.all(zcro == expect) + assert np.all(m.get_cm_ref() == expect) + + zr[1, 2] = 99 + assert np.all(zr == expect) + assert np.all(zrro == expect) + assert np.all(m.get_rm_ref() == expect) + + # Make sure the readonly ones are numpy-readonly: + with pytest.raises(ValueError): + zcro[1, 2] = 6 + with pytest.raises(ValueError): + zrro[1, 2] = 6 + + # We should be able to explicitly copy like this (and since we're copying, + # the const should drop away) + y1 = np.array(m.get_cm_const_ref()) + + assert y1.flags.owndata + assert y1.flags.writeable + # We should get copies of the eigen data, which was modified above: + assert y1[1, 2] == 99 + y1[1, 2] += 12 + assert y1[1, 2] == 111 + assert zc[1, 2] == 99 # Make sure we aren't referencing the original + + +def test_both_ref_mutators(): + """Tests a complex chain of nested eigen/numpy references""" + + m.reset_refs() # In case another test already changed it + + z = m.get_cm_ref() # numpy -> eigen + z[0, 2] -= 3 + z2 = m.incr_matrix(z, 1) # numpy -> eigen -> numpy -> eigen + z2[1, 1] += 6 + z3 = m.incr_matrix(z, 2) # (numpy -> eigen)^3 + z3[2, 2] += -5 + z4 = m.incr_matrix(z, 3) # (numpy -> eigen)^4 + z4[1, 1] -= 1 + z5 = m.incr_matrix(z, 4) # (numpy -> eigen)^5 + z5[0, 0] = 0 + assert np.all(z == z2) + assert np.all(z == z3) + assert np.all(z == z4) + assert np.all(z == z5) + expect = np.array([[0.0, 22, 20], [31, 37, 33], [41, 42, 38]]) + assert np.all(z == expect) + + y = np.array(range(100), dtype="float64").reshape(10, 10) + y2 = m.incr_matrix_any(y, 10) # np -> eigen -> np + y3 = m.incr_matrix_any( + y2[0::2, 0::2], -33 + ) # np -> eigen -> np slice -> np -> eigen -> np + y4 = m.even_rows(y3) # numpy -> eigen slice -> (... y3) + y5 = m.even_cols(y4) # numpy -> eigen slice -> (... y4) + y6 = m.incr_matrix_any(y5, 1000) # numpy -> eigen -> (... y5) + + # Apply same mutations using just numpy: + yexpect = np.array(range(100), dtype="float64").reshape(10, 10) + yexpect += 10 + yexpect[0::2, 0::2] -= 33 + yexpect[0::4, 0::4] += 1000 + assert np.all(y6 == yexpect[0::4, 0::4]) + assert np.all(y5 == yexpect[0::4, 0::4]) + assert np.all(y4 == yexpect[0::4, 0::2]) + assert np.all(y3 == yexpect[0::2, 0::2]) + assert np.all(y2 == yexpect) + assert np.all(y == yexpect) + + +def test_nocopy_wrapper(): + # get_elem requires a column-contiguous matrix reference, but should be + # callable with other types of matrix (via copying): + int_matrix_colmajor = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]], order="F") + dbl_matrix_colmajor = np.array( + int_matrix_colmajor, dtype="double", order="F", copy=True + ) + int_matrix_rowmajor = np.array(int_matrix_colmajor, order="C", copy=True) + dbl_matrix_rowmajor = np.array( + int_matrix_rowmajor, dtype="double", order="C", copy=True + ) + + # All should be callable via get_elem: + assert m.get_elem(int_matrix_colmajor) == 8 + assert m.get_elem(dbl_matrix_colmajor) == 8 + assert m.get_elem(int_matrix_rowmajor) == 8 + assert m.get_elem(dbl_matrix_rowmajor) == 8 + + # All but the second should fail with m.get_elem_nocopy: + with pytest.raises(TypeError) as excinfo: + m.get_elem_nocopy(int_matrix_colmajor) + assert "get_elem_nocopy(): incompatible function arguments." in str(excinfo.value) + assert ", flags.f_contiguous" in str(excinfo.value) + assert m.get_elem_nocopy(dbl_matrix_colmajor) == 8 + with pytest.raises(TypeError) as excinfo: + m.get_elem_nocopy(int_matrix_rowmajor) + assert "get_elem_nocopy(): incompatible function arguments." in str(excinfo.value) + assert ", flags.f_contiguous" in str(excinfo.value) + with pytest.raises(TypeError) as excinfo: + m.get_elem_nocopy(dbl_matrix_rowmajor) + assert "get_elem_nocopy(): incompatible function arguments." in str(excinfo.value) + assert ", flags.f_contiguous" in str(excinfo.value) + + # For the row-major test, we take a long matrix in row-major, so only the third is allowed: + with pytest.raises(TypeError) as excinfo: + m.get_elem_rm_nocopy(int_matrix_colmajor) + assert "get_elem_rm_nocopy(): incompatible function arguments." in str( + excinfo.value + ) + assert ", flags.c_contiguous" in str(excinfo.value) + with pytest.raises(TypeError) as excinfo: + m.get_elem_rm_nocopy(dbl_matrix_colmajor) + assert "get_elem_rm_nocopy(): incompatible function arguments." in str( + excinfo.value + ) + assert ", flags.c_contiguous" in str(excinfo.value) + assert m.get_elem_rm_nocopy(int_matrix_rowmajor) == 8 + with pytest.raises(TypeError) as excinfo: + m.get_elem_rm_nocopy(dbl_matrix_rowmajor) + assert "get_elem_rm_nocopy(): incompatible function arguments." in str( + excinfo.value + ) + assert ", flags.c_contiguous" in str(excinfo.value) + + +def test_eigen_ref_life_support(): + """Ensure the lifetime of temporary arrays created by the `Ref` caster + + The `Ref` caster sometimes creates a copy which needs to stay alive. This needs to + happen both for directs casts (just the array) or indirectly (e.g. list of arrays). + """ + + a = np.full(shape=10, fill_value=8, dtype=np.int8) + assert m.get_elem_direct(a) == 8 + + list_of_a = [a] + assert m.get_elem_indirect(list_of_a) == 8 + + +def test_special_matrix_objects(): + assert np.all(m.incr_diag(7) == np.diag([1.0, 2, 3, 4, 5, 6, 7])) + + asymm = np.array([[1.0, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12], [13, 14, 15, 16]]) + symm_lower = np.array(asymm) + symm_upper = np.array(asymm) + for i in range(4): + for j in range(i + 1, 4): + symm_lower[i, j] = symm_lower[j, i] + symm_upper[j, i] = symm_upper[i, j] + + assert np.all(m.symmetric_lower(asymm) == symm_lower) + assert np.all(m.symmetric_upper(asymm) == symm_upper) + + +def test_dense_signature(doc): + assert ( + doc(m.double_col) + == """ + double_col(arg0: numpy.ndarray[numpy.float32[m, 1]]) -> numpy.ndarray[numpy.float32[m, 1]] + """ + ) + assert ( + doc(m.double_row) + == """ + double_row(arg0: numpy.ndarray[numpy.float32[1, n]]) -> numpy.ndarray[numpy.float32[1, n]] + """ + ) + assert doc(m.double_complex) == ( + """ + double_complex(arg0: numpy.ndarray[numpy.complex64[m, 1]])""" + """ -> numpy.ndarray[numpy.complex64[m, 1]] + """ + ) + assert doc(m.double_mat_rm) == ( + """ + double_mat_rm(arg0: numpy.ndarray[numpy.float32[m, n]])""" + """ -> numpy.ndarray[numpy.float32[m, n]] + """ + ) + + +def test_named_arguments(): + a = np.array([[1.0, 2], [3, 4], [5, 6]]) + b = np.ones((2, 1)) + + assert np.all(m.matrix_multiply(a, b) == np.array([[3.0], [7], [11]])) + assert np.all(m.matrix_multiply(A=a, B=b) == np.array([[3.0], [7], [11]])) + assert np.all(m.matrix_multiply(B=b, A=a) == np.array([[3.0], [7], [11]])) + + with pytest.raises(ValueError) as excinfo: + m.matrix_multiply(b, a) + assert str(excinfo.value) == "Nonconformable matrices!" + + with pytest.raises(ValueError) as excinfo: + m.matrix_multiply(A=b, B=a) + assert str(excinfo.value) == "Nonconformable matrices!" + + with pytest.raises(ValueError) as excinfo: + m.matrix_multiply(B=a, A=b) + assert str(excinfo.value) == "Nonconformable matrices!" + + +def test_sparse(): + pytest.importorskip("scipy") + assert_sparse_equal_ref(m.sparse_r()) + assert_sparse_equal_ref(m.sparse_c()) + assert_sparse_equal_ref(m.sparse_copy_r(m.sparse_r())) + assert_sparse_equal_ref(m.sparse_copy_c(m.sparse_c())) + assert_sparse_equal_ref(m.sparse_copy_r(m.sparse_c())) + assert_sparse_equal_ref(m.sparse_copy_c(m.sparse_r())) + + +def test_sparse_signature(doc): + pytest.importorskip("scipy") + assert ( + doc(m.sparse_copy_r) + == """ + sparse_copy_r(arg0: scipy.sparse.csr_matrix[numpy.float32]) -> scipy.sparse.csr_matrix[numpy.float32] + """ + ) + assert ( + doc(m.sparse_copy_c) + == """ + sparse_copy_c(arg0: scipy.sparse.csc_matrix[numpy.float32]) -> scipy.sparse.csc_matrix[numpy.float32] + """ + ) + + +def test_issue738(): + """Ignore strides on a length-1 dimension (even if they would be incompatible length > 1)""" + assert np.all(m.iss738_f1(np.array([[1.0, 2, 3]])) == np.array([[1.0, 102, 203]])) + assert np.all( + m.iss738_f1(np.array([[1.0], [2], [3]])) == np.array([[1.0], [12], [23]]) + ) + + assert np.all(m.iss738_f2(np.array([[1.0, 2, 3]])) == np.array([[1.0, 102, 203]])) + assert np.all( + m.iss738_f2(np.array([[1.0], [2], [3]])) == np.array([[1.0], [12], [23]]) + ) + + +@pytest.mark.parametrize("func", [m.iss738_f1, m.iss738_f2]) +@pytest.mark.parametrize("sizes", [(0, 2), (2, 0)]) +def test_zero_length(func, sizes): + """Ignore strides on a length-0 dimension (even if they would be incompatible length > 1)""" + assert np.all(func(np.zeros(sizes)) == np.zeros(sizes)) + + +def test_issue1105(): + """Issue 1105: 1xN or Nx1 input arrays weren't accepted for eigen + compile-time row vectors or column vector""" + assert m.iss1105_row(np.ones((1, 7))) + assert m.iss1105_col(np.ones((7, 1))) + + # These should still fail (incompatible dimensions): + with pytest.raises(TypeError) as excinfo: + m.iss1105_row(np.ones((7, 1))) + assert "incompatible function arguments" in str(excinfo.value) + with pytest.raises(TypeError) as excinfo: + m.iss1105_col(np.ones((1, 7))) + assert "incompatible function arguments" in str(excinfo.value) + + +def test_custom_operator_new(): + """Using Eigen types as member variables requires a class-specific + operator new with proper alignment""" + + o = m.CustomOperatorNew() + np.testing.assert_allclose(o.a, 0.0) + np.testing.assert_allclose(o.b.diagonal(), 1.0) diff --git a/pybind11/tests/test_eigen_tensor.cpp b/pybind11/tests/test_eigen_tensor.cpp new file mode 100644 index 000000000..503c69c7d --- /dev/null +++ b/pybind11/tests/test_eigen_tensor.cpp @@ -0,0 +1,18 @@ +/* + tests/eigen_tensor.cpp -- automatic conversion of Eigen Tensor + + All rights reserved. Use of this source code is governed by a + BSD-style license that can be found in the LICENSE file. +*/ + +#define PYBIND11_TEST_EIGEN_TENSOR_NAMESPACE eigen_tensor + +#ifdef EIGEN_AVOID_STL_ARRAY +# undef EIGEN_AVOID_STL_ARRAY +#endif + +#include "test_eigen_tensor.inl" + +#include "pybind11_tests.h" + +test_initializer egien_tensor("eigen_tensor", eigen_tensor_test::test_module); diff --git a/pybind11/tests/test_eigen_tensor.inl b/pybind11/tests/test_eigen_tensor.inl new file mode 100644 index 000000000..d864ce737 --- /dev/null +++ b/pybind11/tests/test_eigen_tensor.inl @@ -0,0 +1,333 @@ +/* + tests/eigen_tensor.cpp -- automatic conversion of Eigen Tensor + + All rights reserved. Use of this source code is governed by a + BSD-style license that can be found in the LICENSE file. +*/ + +#include + +PYBIND11_NAMESPACE_BEGIN(eigen_tensor_test) + +namespace py = pybind11; + +PYBIND11_WARNING_DISABLE_MSVC(4127) + +template +void reset_tensor(M &x) { + for (int i = 0; i < x.dimension(0); i++) { + for (int j = 0; j < x.dimension(1); j++) { + for (int k = 0; k < x.dimension(2); k++) { + x(i, j, k) = i * (5 * 2) + j * 2 + k; + } + } + } +} + +template +bool check_tensor(M &x) { + for (int i = 0; i < x.dimension(0); i++) { + for (int j = 0; j < x.dimension(1); j++) { + for (int k = 0; k < x.dimension(2); k++) { + if (x(i, j, k) != (i * (5 * 2) + j * 2 + k)) { + return false; + } + } + } + } + return true; +} + +template +Eigen::Tensor &get_tensor() { + static Eigen::Tensor *x; + + if (!x) { + x = new Eigen::Tensor(3, 5, 2); + reset_tensor(*x); + } + + return *x; +} + +template +Eigen::TensorMap> &get_tensor_map() { + static Eigen::TensorMap> *x; + + if (!x) { + x = new Eigen::TensorMap>(get_tensor()); + } + + return *x; +} + +template +Eigen::TensorFixedSize, Options> &get_fixed_tensor() { + static Eigen::TensorFixedSize, Options> *x; + + if (!x) { + Eigen::aligned_allocator, Options>> + allocator; + x = new (allocator.allocate(1)) + Eigen::TensorFixedSize, Options>(); + reset_tensor(*x); + } + + return *x; +} + +template +const Eigen::Tensor &get_const_tensor() { + return get_tensor(); +} + +template +struct CustomExample { + CustomExample() : member(get_tensor()), view_member(member) {} + + Eigen::Tensor member; + Eigen::TensorMap> view_member; +}; + +template +void init_tensor_module(pybind11::module &m) { + const char *needed_options = ""; + if (Options == Eigen::ColMajor) { + needed_options = "F"; + } else { + needed_options = "C"; + } + m.attr("needed_options") = needed_options; + + m.def("setup", []() { + reset_tensor(get_tensor()); + reset_tensor(get_fixed_tensor()); + }); + + m.def("is_ok", []() { + return check_tensor(get_tensor()) && check_tensor(get_fixed_tensor()); + }); + + py::class_>(m, "CustomExample", py::module_local()) + .def(py::init<>()) + .def_readonly( + "member", &CustomExample::member, py::return_value_policy::reference_internal) + .def_readonly("member_view", + &CustomExample::view_member, + py::return_value_policy::reference_internal); + + m.def( + "copy_fixed_tensor", + []() { return &get_fixed_tensor(); }, + py::return_value_policy::copy); + + m.def( + "copy_tensor", []() { return &get_tensor(); }, py::return_value_policy::copy); + + m.def( + "copy_const_tensor", + []() { return &get_const_tensor(); }, + py::return_value_policy::copy); + + m.def( + "move_fixed_tensor_copy", + []() -> Eigen::TensorFixedSize, Options> { + return get_fixed_tensor(); + }, + py::return_value_policy::move); + + m.def( + "move_tensor_copy", + []() -> Eigen::Tensor { return get_tensor(); }, + py::return_value_policy::move); + + m.def( + "move_const_tensor", + []() -> const Eigen::Tensor & { return get_const_tensor(); }, + py::return_value_policy::move); + + m.def( + "take_fixed_tensor", + + []() { + Eigen::aligned_allocator< + Eigen::TensorFixedSize, Options>> + allocator; + return new (allocator.allocate(1)) + Eigen::TensorFixedSize, Options>( + get_fixed_tensor()); + }, + py::return_value_policy::take_ownership); + + m.def( + "take_tensor", + []() { return new Eigen::Tensor(get_tensor()); }, + py::return_value_policy::take_ownership); + + m.def( + "take_const_tensor", + []() -> const Eigen::Tensor * { + return new Eigen::Tensor(get_tensor()); + }, + py::return_value_policy::take_ownership); + + m.def( + "take_view_tensor", + []() -> const Eigen::TensorMap> * { + return new Eigen::TensorMap>(get_tensor()); + }, + py::return_value_policy::take_ownership); + + m.def( + "reference_tensor", + []() { return &get_tensor(); }, + py::return_value_policy::reference); + + m.def( + "reference_tensor_v2", + []() -> Eigen::Tensor & { return get_tensor(); }, + py::return_value_policy::reference); + + m.def( + "reference_tensor_internal", + []() { return &get_tensor(); }, + py::return_value_policy::reference_internal); + + m.def( + "reference_fixed_tensor", + []() { return &get_tensor(); }, + py::return_value_policy::reference); + + m.def( + "reference_const_tensor", + []() { return &get_const_tensor(); }, + py::return_value_policy::reference); + + m.def( + "reference_const_tensor_v2", + []() -> const Eigen::Tensor & { return get_const_tensor(); }, + py::return_value_policy::reference); + + m.def( + "reference_view_of_tensor", + []() -> Eigen::TensorMap> { + return get_tensor_map(); + }, + py::return_value_policy::reference); + + m.def( + "reference_view_of_tensor_v2", + // NOLINTNEXTLINE(readability-const-return-type) + []() -> const Eigen::TensorMap> { + return get_tensor_map(); // NOLINT(readability-const-return-type) + }, // NOLINT(readability-const-return-type) + py::return_value_policy::reference); + + m.def( + "reference_view_of_tensor_v3", + []() -> Eigen::TensorMap> * { + return &get_tensor_map(); + }, + py::return_value_policy::reference); + + m.def( + "reference_view_of_tensor_v4", + []() -> const Eigen::TensorMap> * { + return &get_tensor_map(); + }, + py::return_value_policy::reference); + + m.def( + "reference_view_of_tensor_v5", + []() -> Eigen::TensorMap> & { + return get_tensor_map(); + }, + py::return_value_policy::reference); + + m.def( + "reference_view_of_tensor_v6", + []() -> const Eigen::TensorMap> & { + return get_tensor_map(); + }, + py::return_value_policy::reference); + + m.def( + "reference_view_of_fixed_tensor", + []() { + return Eigen::TensorMap< + Eigen::TensorFixedSize, Options>>( + get_fixed_tensor()); + }, + py::return_value_policy::reference); + + m.def("round_trip_tensor", + [](const Eigen::Tensor &tensor) { return tensor; }); + + m.def( + "round_trip_tensor_noconvert", + [](const Eigen::Tensor &tensor) { return tensor; }, + py::arg("tensor").noconvert()); + + m.def("round_trip_tensor2", + [](const Eigen::Tensor &tensor) { return tensor; }); + + m.def("round_trip_fixed_tensor", + [](const Eigen::TensorFixedSize, Options> &tensor) { + return tensor; + }); + + m.def( + "round_trip_view_tensor", + [](Eigen::TensorMap> view) { return view; }, + py::return_value_policy::reference); + + m.def( + "round_trip_view_tensor_ref", + [](Eigen::TensorMap> &view) { return view; }, + py::return_value_policy::reference); + + m.def( + "round_trip_view_tensor_ptr", + [](Eigen::TensorMap> *view) { return view; }, + py::return_value_policy::reference); + + m.def( + "round_trip_aligned_view_tensor", + [](Eigen::TensorMap, Eigen::Aligned> view) { + return view; + }, + py::return_value_policy::reference); + + m.def( + "round_trip_const_view_tensor", + [](Eigen::TensorMap> view) { + return Eigen::Tensor(view); + }, + py::return_value_policy::move); + + m.def( + "round_trip_rank_0", + [](const Eigen::Tensor &tensor) { return tensor; }, + py::return_value_policy::move); + + m.def( + "round_trip_rank_0_noconvert", + [](const Eigen::Tensor &tensor) { return tensor; }, + py::arg("tensor").noconvert(), + py::return_value_policy::move); + + m.def( + "round_trip_rank_0_view", + [](Eigen::TensorMap> &tensor) { return tensor; }, + py::return_value_policy::reference); +} + +void test_module(py::module_ &m) { + auto f_style = m.def_submodule("f_style"); + auto c_style = m.def_submodule("c_style"); + + init_tensor_module(f_style); + init_tensor_module(c_style); +} + +PYBIND11_NAMESPACE_END(eigen_tensor_test) diff --git a/pybind11/tests/test_eigen_tensor.py b/pybind11/tests/test_eigen_tensor.py new file mode 100644 index 000000000..3e7ee6b7f --- /dev/null +++ b/pybind11/tests/test_eigen_tensor.py @@ -0,0 +1,288 @@ +import sys + +import pytest + +np = pytest.importorskip("numpy") +eigen_tensor = pytest.importorskip("pybind11_tests.eigen_tensor") +submodules = [eigen_tensor.c_style, eigen_tensor.f_style] +try: + import eigen_tensor_avoid_stl_array as avoid + + submodules += [avoid.c_style, avoid.f_style] +except ImportError as e: + # Ensure config, build, toolchain, etc. issues are not masked here: + msg = ( + "import eigen_tensor_avoid_stl_array FAILED, while " + "import pybind11_tests.eigen_tensor succeeded. " + "Please ensure that " + "test_eigen_tensor.cpp & " + "eigen_tensor_avoid_stl_array.cpp " + "are built together (or both are not built if Eigen is not available)." + ) + raise RuntimeError(msg) from e + +tensor_ref = np.empty((3, 5, 2), dtype=np.int64) + +for i in range(tensor_ref.shape[0]): + for j in range(tensor_ref.shape[1]): + for k in range(tensor_ref.shape[2]): + tensor_ref[i, j, k] = i * (5 * 2) + j * 2 + k + +indices = (2, 3, 1) + + +@pytest.fixture(autouse=True) +def cleanup(): + for module in submodules: + module.setup() + + yield + + for module in submodules: + assert module.is_ok() + + +def test_import_avoid_stl_array(): + pytest.importorskip("eigen_tensor_avoid_stl_array") + assert len(submodules) == 4 + + +def assert_equal_tensor_ref(mat, writeable=True, modified=None): + assert mat.flags.writeable == writeable + + copy = np.array(tensor_ref) + if modified is not None: + copy[indices] = modified + + np.testing.assert_array_equal(mat, copy) + + +@pytest.mark.parametrize("m", submodules) +@pytest.mark.parametrize("member_name", ["member", "member_view"]) +def test_reference_internal(m, member_name): + if not hasattr(sys, "getrefcount"): + pytest.skip("No reference counting") + foo = m.CustomExample() + counts = sys.getrefcount(foo) + mem = getattr(foo, member_name) + assert_equal_tensor_ref(mem, writeable=False) + new_counts = sys.getrefcount(foo) + assert new_counts == counts + 1 + assert_equal_tensor_ref(mem, writeable=False) + del mem + assert sys.getrefcount(foo) == counts + + +assert_equal_funcs = [ + "copy_tensor", + "copy_fixed_tensor", + "copy_const_tensor", + "move_tensor_copy", + "move_fixed_tensor_copy", + "take_tensor", + "take_fixed_tensor", + "reference_tensor", + "reference_tensor_v2", + "reference_fixed_tensor", + "reference_view_of_tensor", + "reference_view_of_tensor_v3", + "reference_view_of_tensor_v5", + "reference_view_of_fixed_tensor", +] + +assert_equal_const_funcs = [ + "reference_view_of_tensor_v2", + "reference_view_of_tensor_v4", + "reference_view_of_tensor_v6", + "reference_const_tensor", + "reference_const_tensor_v2", +] + + +@pytest.mark.parametrize("m", submodules) +@pytest.mark.parametrize("func_name", assert_equal_funcs + assert_equal_const_funcs) +def test_convert_tensor_to_py(m, func_name): + writeable = func_name in assert_equal_funcs + assert_equal_tensor_ref(getattr(m, func_name)(), writeable=writeable) + + +@pytest.mark.parametrize("m", submodules) +def test_bad_cpp_to_python_casts(m): + with pytest.raises( + RuntimeError, match="Cannot use reference internal when there is no parent" + ): + m.reference_tensor_internal() + + with pytest.raises(RuntimeError, match="Cannot move from a constant reference"): + m.move_const_tensor() + + with pytest.raises( + RuntimeError, match="Cannot take ownership of a const reference" + ): + m.take_const_tensor() + + with pytest.raises( + RuntimeError, + match="Invalid return_value_policy for Eigen Map type, must be either reference or reference_internal", + ): + m.take_view_tensor() + + +@pytest.mark.parametrize("m", submodules) +def test_bad_python_to_cpp_casts(m): + with pytest.raises( + TypeError, match=r"^round_trip_tensor\(\): incompatible function arguments" + ): + m.round_trip_tensor(np.zeros((2, 3))) + + with pytest.raises(TypeError, match=r"^Cannot cast array data from dtype"): + m.round_trip_tensor(np.zeros(dtype=np.str_, shape=(2, 3, 1))) + + with pytest.raises( + TypeError, + match=r"^round_trip_tensor_noconvert\(\): incompatible function arguments", + ): + m.round_trip_tensor_noconvert(tensor_ref) + + assert_equal_tensor_ref( + m.round_trip_tensor_noconvert(tensor_ref.astype(np.float64)) + ) + + bad_options = "C" if m.needed_options == "F" else "F" + # Shape, dtype and the order need to be correct for a TensorMap cast + with pytest.raises( + TypeError, match=r"^round_trip_view_tensor\(\): incompatible function arguments" + ): + m.round_trip_view_tensor( + np.zeros((3, 5, 2), dtype=np.float64, order=bad_options) + ) + + with pytest.raises( + TypeError, match=r"^round_trip_view_tensor\(\): incompatible function arguments" + ): + m.round_trip_view_tensor( + np.zeros((3, 5, 2), dtype=np.float32, order=m.needed_options) + ) + + with pytest.raises( + TypeError, match=r"^round_trip_view_tensor\(\): incompatible function arguments" + ): + m.round_trip_view_tensor( + np.zeros((3, 5), dtype=np.float64, order=m.needed_options) + ) + + temp = np.zeros((3, 5, 2), dtype=np.float64, order=m.needed_options) + with pytest.raises( + TypeError, match=r"^round_trip_view_tensor\(\): incompatible function arguments" + ): + m.round_trip_view_tensor( + temp[:, ::-1, :], + ) + + temp = np.zeros((3, 5, 2), dtype=np.float64, order=m.needed_options) + temp.setflags(write=False) + with pytest.raises( + TypeError, match=r"^round_trip_view_tensor\(\): incompatible function arguments" + ): + m.round_trip_view_tensor(temp) + + +@pytest.mark.parametrize("m", submodules) +def test_references_actually_refer(m): + a = m.reference_tensor() + temp = a[indices] + a[indices] = 100 + assert_equal_tensor_ref(m.copy_const_tensor(), modified=100) + a[indices] = temp + assert_equal_tensor_ref(m.copy_const_tensor()) + + a = m.reference_view_of_tensor() + a[indices] = 100 + assert_equal_tensor_ref(m.copy_const_tensor(), modified=100) + a[indices] = temp + assert_equal_tensor_ref(m.copy_const_tensor()) + + +@pytest.mark.parametrize("m", submodules) +def test_round_trip(m): + assert_equal_tensor_ref(m.round_trip_tensor(tensor_ref)) + + with pytest.raises(TypeError, match="^Cannot cast array data from"): + assert_equal_tensor_ref(m.round_trip_tensor2(tensor_ref)) + + assert_equal_tensor_ref(m.round_trip_tensor2(np.array(tensor_ref, dtype=np.int32))) + assert_equal_tensor_ref(m.round_trip_fixed_tensor(tensor_ref)) + assert_equal_tensor_ref(m.round_trip_aligned_view_tensor(m.reference_tensor())) + + copy = np.array(tensor_ref, dtype=np.float64, order=m.needed_options) + assert_equal_tensor_ref(m.round_trip_view_tensor(copy)) + assert_equal_tensor_ref(m.round_trip_view_tensor_ref(copy)) + assert_equal_tensor_ref(m.round_trip_view_tensor_ptr(copy)) + copy.setflags(write=False) + assert_equal_tensor_ref(m.round_trip_const_view_tensor(copy)) + + np.testing.assert_array_equal( + tensor_ref[:, ::-1, :], m.round_trip_tensor(tensor_ref[:, ::-1, :]) + ) + + assert m.round_trip_rank_0(np.float64(3.5)) == 3.5 + assert m.round_trip_rank_0(3.5) == 3.5 + + with pytest.raises( + TypeError, + match=r"^round_trip_rank_0_noconvert\(\): incompatible function arguments", + ): + m.round_trip_rank_0_noconvert(np.float64(3.5)) + + with pytest.raises( + TypeError, + match=r"^round_trip_rank_0_noconvert\(\): incompatible function arguments", + ): + m.round_trip_rank_0_noconvert(3.5) + + with pytest.raises( + TypeError, match=r"^round_trip_rank_0_view\(\): incompatible function arguments" + ): + m.round_trip_rank_0_view(np.float64(3.5)) + + with pytest.raises( + TypeError, match=r"^round_trip_rank_0_view\(\): incompatible function arguments" + ): + m.round_trip_rank_0_view(3.5) + + +@pytest.mark.parametrize("m", submodules) +def test_round_trip_references_actually_refer(m): + # Need to create a copy that matches the type on the C side + copy = np.array(tensor_ref, dtype=np.float64, order=m.needed_options) + a = m.round_trip_view_tensor(copy) + temp = a[indices] + a[indices] = 100 + assert_equal_tensor_ref(copy, modified=100) + a[indices] = temp + assert_equal_tensor_ref(copy) + + +@pytest.mark.parametrize("m", submodules) +def test_doc_string(m, doc): + assert ( + doc(m.copy_tensor) == "copy_tensor() -> numpy.ndarray[numpy.float64[?, ?, ?]]" + ) + assert ( + doc(m.copy_fixed_tensor) + == "copy_fixed_tensor() -> numpy.ndarray[numpy.float64[3, 5, 2]]" + ) + assert ( + doc(m.reference_const_tensor) + == "reference_const_tensor() -> numpy.ndarray[numpy.float64[?, ?, ?]]" + ) + + order_flag = f"flags.{m.needed_options.lower()}_contiguous" + assert doc(m.round_trip_view_tensor) == ( + f"round_trip_view_tensor(arg0: numpy.ndarray[numpy.float64[?, ?, ?], flags.writeable, {order_flag}])" + f" -> numpy.ndarray[numpy.float64[?, ?, ?], flags.writeable, {order_flag}]" + ) + assert doc(m.round_trip_const_view_tensor) == ( + f"round_trip_const_view_tensor(arg0: numpy.ndarray[numpy.float64[?, ?, ?], {order_flag}])" + " -> numpy.ndarray[numpy.float64[?, ?, ?]]" + ) diff --git a/pybind11/tests/test_embed/catch.cpp b/pybind11/tests/test_embed/catch.cpp index 96d2e3f92..558a7a35e 100644 --- a/pybind11/tests/test_embed/catch.cpp +++ b/pybind11/tests/test_embed/catch.cpp @@ -3,11 +3,9 @@ #include -#ifdef _MSC_VER // Silence MSVC C++17 deprecation warning from Catch regarding std::uncaught_exceptions (up to // catch 2.0.1; this should be fixed in the next catch release after 2.0.1). -# pragma warning(disable : 4996) -#endif +PYBIND11_WARNING_DISABLE_MSVC(4996) // Catch uses _ internally, which breaks gettext style defines #ifdef _ @@ -20,7 +18,25 @@ namespace py = pybind11; int main(int argc, char *argv[]) { + // Setup for TEST_CASE in test_interpreter.cpp, tagging on a large random number: + std::string updated_pythonpath("pybind11_test_embed_PYTHONPATH_2099743835476552"); + const char *preexisting_pythonpath = getenv("PYTHONPATH"); + if (preexisting_pythonpath != nullptr) { +#if defined(_WIN32) + updated_pythonpath += ';'; +#else + updated_pythonpath += ':'; +#endif + updated_pythonpath += preexisting_pythonpath; + } +#if defined(_WIN32) + _putenv_s("PYTHONPATH", updated_pythonpath.c_str()); +#else + setenv("PYTHONPATH", updated_pythonpath.c_str(), /*replace=*/1); +#endif + py::scoped_interpreter guard{}; + auto result = Catch::Session().run(argc, argv); return result < 0xff ? result : 0xff; diff --git a/pybind11/tests/test_embed/test_interpreter.cpp b/pybind11/tests/test_embed/test_interpreter.cpp index 1c45457a0..c6c8a22d9 100644 --- a/pybind11/tests/test_embed/test_interpreter.cpp +++ b/pybind11/tests/test_embed/test_interpreter.cpp @@ -1,10 +1,8 @@ #include -#ifdef _MSC_VER // Silence MSVC C++17 deprecation warning from Catch regarding std::uncaught_exceptions (up to // catch 2.0.1; this should be fixed in the next catch release after 2.0.1). -# pragma warning(disable : 4996) -#endif +PYBIND11_WARNING_DISABLE_MSVC(4996) #include #include @@ -16,6 +14,11 @@ namespace py = pybind11; using namespace py::literals; +size_t get_sys_path_size() { + auto sys_path = py::module::import("sys").attr("path"); + return py::len(sys_path); +} + class Widget { public: explicit Widget(std::string message) : message(std::move(message)) {} @@ -75,6 +78,13 @@ PYBIND11_EMBEDDED_MODULE(throw_error_already_set, ) { d["missing"].cast(); } +TEST_CASE("PYTHONPATH is used to update sys.path") { + // The setup for this TEST_CASE is in catch.cpp! + auto sys_path = py::str(py::module_::import("sys").attr("path")).cast(); + REQUIRE_THAT(sys_path, + Catch::Matchers::Contains("pybind11_test_embed_PYTHONPATH_2099743835476552")); +} + TEST_CASE("Pass classes and data between modules defined in C++ and Python") { auto module_ = py::module_::import("test_interpreter"); REQUIRE(py::hasattr(module_, "DerivedWidget")); @@ -161,10 +171,94 @@ TEST_CASE("There can be only one interpreter") { py::initialize_interpreter(); } -bool has_pybind11_internals_builtin() { - auto builtins = py::handle(PyEval_GetBuiltins()); - return builtins.contains(PYBIND11_INTERNALS_ID); -}; +#if PY_VERSION_HEX >= PYBIND11_PYCONFIG_SUPPORT_PY_VERSION_HEX +TEST_CASE("Custom PyConfig") { + py::finalize_interpreter(); + PyConfig config; + PyConfig_InitPythonConfig(&config); + REQUIRE_NOTHROW(py::scoped_interpreter{&config}); + { + py::scoped_interpreter p{&config}; + REQUIRE(py::module_::import("widget_module").attr("add")(1, 41).cast() == 42); + } + py::initialize_interpreter(); +} + +TEST_CASE("scoped_interpreter with PyConfig_InitIsolatedConfig and argv") { + py::finalize_interpreter(); + { + PyConfig config; + PyConfig_InitIsolatedConfig(&config); + char *argv[] = {strdup("a.out")}; + py::scoped_interpreter argv_scope{&config, 1, argv}; + std::free(argv[0]); + auto module = py::module::import("test_interpreter"); + auto py_widget = module.attr("DerivedWidget")("The question"); + const auto &cpp_widget = py_widget.cast(); + REQUIRE(cpp_widget.argv0() == "a.out"); + } + py::initialize_interpreter(); +} + +TEST_CASE("scoped_interpreter with PyConfig_InitPythonConfig and argv") { + py::finalize_interpreter(); + { + PyConfig config; + PyConfig_InitPythonConfig(&config); + + // `initialize_interpreter() overrides the default value for config.parse_argv (`1`) by + // changing it to `0`. This test exercises `scoped_interpreter` with the default config. + char *argv[] = {strdup("a.out"), strdup("arg1")}; + py::scoped_interpreter argv_scope(&config, 2, argv); + std::free(argv[0]); + std::free(argv[1]); + auto module = py::module::import("test_interpreter"); + auto py_widget = module.attr("DerivedWidget")("The question"); + const auto &cpp_widget = py_widget.cast(); + REQUIRE(cpp_widget.argv0() == "arg1"); + } + py::initialize_interpreter(); +} +#endif + +TEST_CASE("Add program dir to path pre-PyConfig") { + py::finalize_interpreter(); + size_t path_size_add_program_dir_to_path_false = 0; + { + py::scoped_interpreter scoped_interp{true, 0, nullptr, false}; + path_size_add_program_dir_to_path_false = get_sys_path_size(); + } + { + py::scoped_interpreter scoped_interp{}; + REQUIRE(get_sys_path_size() == path_size_add_program_dir_to_path_false + 1); + } + py::initialize_interpreter(); +} + +#if PY_VERSION_HEX >= PYBIND11_PYCONFIG_SUPPORT_PY_VERSION_HEX +TEST_CASE("Add program dir to path using PyConfig") { + py::finalize_interpreter(); + size_t path_size_add_program_dir_to_path_false = 0; + { + PyConfig config; + PyConfig_InitPythonConfig(&config); + py::scoped_interpreter scoped_interp{&config, 0, nullptr, false}; + path_size_add_program_dir_to_path_false = get_sys_path_size(); + } + { + PyConfig config; + PyConfig_InitPythonConfig(&config); + py::scoped_interpreter scoped_interp{&config}; + REQUIRE(get_sys_path_size() == path_size_add_program_dir_to_path_false + 1); + } + py::initialize_interpreter(); +} +#endif + +bool has_state_dict_internals_obj() { + return bool( + py::detail::get_internals_obj_from_state_dict(py::detail::get_python_state_dict())); +} bool has_pybind11_internals_static() { auto **&ipp = py::detail::get_internals_pp(); @@ -174,7 +268,7 @@ bool has_pybind11_internals_static() { TEST_CASE("Restart the interpreter") { // Verify pre-restart state. REQUIRE(py::module_::import("widget_module").attr("add")(1, 2).cast() == 3); - REQUIRE(has_pybind11_internals_builtin()); + REQUIRE(has_state_dict_internals_obj()); REQUIRE(has_pybind11_internals_static()); REQUIRE(py::module_::import("external_module").attr("A")(123).attr("value").cast() == 123); @@ -191,10 +285,10 @@ TEST_CASE("Restart the interpreter") { REQUIRE(Py_IsInitialized() == 1); // Internals are deleted after a restart. - REQUIRE_FALSE(has_pybind11_internals_builtin()); + REQUIRE_FALSE(has_state_dict_internals_obj()); REQUIRE_FALSE(has_pybind11_internals_static()); pybind11::detail::get_internals(); - REQUIRE(has_pybind11_internals_builtin()); + REQUIRE(has_state_dict_internals_obj()); REQUIRE(has_pybind11_internals_static()); REQUIRE(reinterpret_cast(*py::detail::get_internals_pp()) == py::module_::import("external_module").attr("internals_at")().cast()); @@ -209,13 +303,13 @@ TEST_CASE("Restart the interpreter") { py::detail::get_internals(); *static_cast(ran) = true; }); - REQUIRE_FALSE(has_pybind11_internals_builtin()); + REQUIRE_FALSE(has_state_dict_internals_obj()); REQUIRE_FALSE(has_pybind11_internals_static()); REQUIRE_FALSE(ran); py::finalize_interpreter(); REQUIRE(ran); py::initialize_interpreter(); - REQUIRE_FALSE(has_pybind11_internals_builtin()); + REQUIRE_FALSE(has_state_dict_internals_obj()); REQUIRE_FALSE(has_pybind11_internals_static()); // C++ modules can be reloaded. @@ -237,7 +331,7 @@ TEST_CASE("Subinterpreter") { REQUIRE(m.attr("add")(1, 2).cast() == 3); } - REQUIRE(has_pybind11_internals_builtin()); + REQUIRE(has_state_dict_internals_obj()); REQUIRE(has_pybind11_internals_static()); /// Create and switch to a subinterpreter. @@ -247,7 +341,7 @@ TEST_CASE("Subinterpreter") { // Subinterpreters get their own copy of builtins. detail::get_internals() still // works by returning from the static variable, i.e. all interpreters share a single // global pybind11::internals; - REQUIRE_FALSE(has_pybind11_internals_builtin()); + REQUIRE_FALSE(has_state_dict_internals_obj()); REQUIRE(has_pybind11_internals_static()); // Modules tags should be gone. @@ -286,7 +380,6 @@ TEST_CASE("Threads") { { py::gil_scoped_release gil_release{}; - REQUIRE(has_pybind11_internals_static()); auto threads = std::vector(); for (auto i = 0; i < num_threads; ++i) { diff --git a/pybind11/tests/test_enum.py b/pybind11/tests/test_enum.py index f14a72398..4e85d29c3 100644 --- a/pybind11/tests/test_enum.py +++ b/pybind11/tests/test_enum.py @@ -1,3 +1,5 @@ +# ruff: noqa: SIM201 SIM300 SIM202 + import pytest from pybind11_tests import enums as m diff --git a/pybind11/tests/test_exceptions.cpp b/pybind11/tests/test_exceptions.cpp index 3ec999d1d..854c7e6f7 100644 --- a/pybind11/tests/test_exceptions.cpp +++ b/pybind11/tests/test_exceptions.cpp @@ -105,11 +105,6 @@ struct PythonAlreadySetInDestructor { py::str s; }; -std::string error_already_set_what(const py::object &exc_type, const py::object &exc_value) { - PyErr_SetObject(exc_type.ptr(), exc_value.ptr()); - return py::error_already_set().what(); -} - TEST_SUBMODULE(exceptions, m) { m.def("throw_std_exception", []() { throw std::runtime_error("This exception was intentionally thrown."); }); @@ -334,4 +329,19 @@ TEST_SUBMODULE(exceptions, m) { e.restore(); } }); + + // https://github.com/pybind/pybind11/issues/4075 + m.def("test_pypy_oserror_normalization", []() { + try { + py::module_::import("io").attr("open")("this_filename_must_not_exist", "r"); + } catch (const py::error_already_set &e) { + return py::str(e.what()); // str must be built before e goes out of scope. + } + return py::str("UNEXPECTED"); + }); + + m.def("test_fn_cast_int", [](const py::function &fn) { + // function returns None instead of int, should give a useful error message + fn().cast(); + }); } diff --git a/pybind11/tests/test_exceptions.py b/pybind11/tests/test_exceptions.py index a5984a142..ccac4536d 100644 --- a/pybind11/tests/test_exceptions.py +++ b/pybind11/tests/test_exceptions.py @@ -4,6 +4,7 @@ import pytest import env import pybind11_cross_module_tests as cm +import pybind11_tests # noqa: F401 from pybind11_tests import exceptions as m @@ -72,9 +73,9 @@ def test_cross_module_exceptions(msg): # TODO: FIXME @pytest.mark.xfail( - "env.PYPY and env.MACOS", + "env.MACOS and (env.PYPY or pybind11_tests.compiler_info.startswith('Homebrew Clang'))", raises=RuntimeError, - reason="Expected failure with PyPy and libc++ (Issue #2847 & PR #2999)", + reason="See Issue #2847, PR #2999, PR #4324", ) def test_cross_module_exception_translator(): with pytest.raises(KeyError): @@ -93,8 +94,7 @@ def ignore_pytest_unraisable_warning(f): if hasattr(pytest, unraisable): # Python >= 3.8 and pytest >= 6 dec = pytest.mark.filterwarnings(f"ignore::pytest.{unraisable}") return dec(f) - else: - return f + return f # TODO: find out why this fails on PyPy, https://foss.heptapod.net/pypy/pypy/-/issues/3583 @@ -182,11 +182,11 @@ def test_custom(msg): m.throws5_1() assert msg(excinfo.value) == "MyException5 subclass" - with pytest.raises(m.MyException5) as excinfo: + with pytest.raises(m.MyException5) as excinfo: # noqa: PT012 try: m.throws5() - except m.MyException5_1: - raise RuntimeError("Exception error: caught child from parent") + except m.MyException5_1 as err: + raise RuntimeError("Exception error: caught child from parent") from err assert msg(excinfo.value) == "this is a helper-defined translated exception" @@ -211,7 +211,7 @@ def test_nested_throws(capture): m.try_catch(m.MyException5, throw_myex) assert str(excinfo.value) == "nested error" - def pycatch(exctype, f, *args): + def pycatch(exctype, f, *args): # noqa: ARG001 try: f(*args) except m.MyException as e: @@ -275,6 +275,20 @@ def test_local_translator(msg): assert msg(excinfo.value) == "this mod" +def test_error_already_set_message_with_unicode_surrogate(): # Issue #4288 + assert m.error_already_set_what(RuntimeError, "\ud927") == ( + "RuntimeError: \\ud927", + False, + ) + + +def test_error_already_set_message_with_malformed_utf8(): + assert m.error_already_set_what(RuntimeError, b"\x80") == ( + "RuntimeError: b'\\x80'", + False, + ) + + class FlakyException(Exception): def __init__(self, failure_point): if failure_point == "failure_point_init": @@ -288,12 +302,12 @@ class FlakyException(Exception): @pytest.mark.parametrize( - "exc_type, exc_value, expected_what", - ( + ("exc_type", "exc_value", "expected_what"), + [ (ValueError, "plain_str", "ValueError: plain_str"), (ValueError, ("tuple_elem",), "ValueError: tuple_elem"), (FlakyException, ("happy",), "FlakyException: FlakyException.__str__"), - ), + ], ) def test_error_already_set_what_with_happy_exceptions( exc_type, exc_value, expected_what @@ -303,8 +317,7 @@ def test_error_already_set_what_with_happy_exceptions( assert what == expected_what -@pytest.mark.skipif("env.PYPY", reason="PyErr_NormalizeException Segmentation fault") -def test_flaky_exception_failure_point_init(): +def _test_flaky_exception_failure_point_init_before_py_3_12(): with pytest.raises(RuntimeError) as excinfo: m.error_already_set_what(FlakyException, ("failure_point_init",)) lines = str(excinfo.value).splitlines() @@ -318,7 +331,33 @@ def test_flaky_exception_failure_point_init(): # Checking the first two lines of the traceback as formatted in error_string(): assert "test_exceptions.py(" in lines[3] assert lines[3].endswith("): __init__") - assert lines[4].endswith("): test_flaky_exception_failure_point_init") + assert lines[4].endswith( + "): _test_flaky_exception_failure_point_init_before_py_3_12" + ) + + +def _test_flaky_exception_failure_point_init_py_3_12(): + # Behavior change in Python 3.12: https://github.com/python/cpython/issues/102594 + what, py_err_set_after_what = m.error_already_set_what( + FlakyException, ("failure_point_init",) + ) + assert not py_err_set_after_what + lines = what.splitlines() + assert lines[0].endswith("ValueError[WITH __notes__]: triggered_failure_point_init") + assert lines[1] == "__notes__ (len=1):" + assert "Normalization failed:" in lines[2] + assert "FlakyException" in lines[2] + + +@pytest.mark.skipif( + "env.PYPY and sys.version_info[:2] < (3, 12)", + reason="PyErr_NormalizeException Segmentation fault", +) +def test_flaky_exception_failure_point_init(): + if sys.version_info[:2] < (3, 12): + _test_flaky_exception_failure_point_init_before_py_3_12() + else: + _test_flaky_exception_failure_point_init_py_3_12() def test_flaky_exception_failure_point_str(): @@ -327,10 +366,7 @@ def test_flaky_exception_failure_point_str(): ) assert not py_err_set_after_what lines = what.splitlines() - if env.PYPY and len(lines) == 3: - n = 3 # Traceback is missing. - else: - n = 5 + n = 3 if env.PYPY and len(lines) == 3 else 5 assert ( lines[:n] == [ @@ -360,3 +396,18 @@ def test_error_already_set_double_restore(): "Internal error: pybind11::detail::error_fetch_and_normalize::restore()" " called a second time. ORIGINAL ERROR: ValueError: Random error." ) + + +def test_pypy_oserror_normalization(): + # https://github.com/pybind/pybind11/issues/4075 + what = m.test_pypy_oserror_normalization() + assert "this_filename_must_not_exist" in what + + +def test_fn_cast_int_exception(): + with pytest.raises(RuntimeError) as excinfo: + m.test_fn_cast_int(lambda: None) + + assert str(excinfo.value).startswith( + "Unable to cast Python instance of type to C++ type" + ) diff --git a/pybind11/tests/test_factory_constructors.py b/pybind11/tests/test_factory_constructors.py index 120a587c4..04df80260 100644 --- a/pybind11/tests/test_factory_constructors.py +++ b/pybind11/tests/test_factory_constructors.py @@ -96,7 +96,7 @@ def test_init_factory_signature(msg): 3. __init__(self: m.factory_constructors.TestFactory1, arg0: m.factory_constructors.tag.pointer_tag) -> None 4. __init__(self: m.factory_constructors.TestFactory1, arg0: handle, arg1: int, arg2: handle) -> None - """ # noqa: E501 line too long + """ ) diff --git a/pybind11/tests/test_gil_scoped.cpp b/pybind11/tests/test_gil_scoped.cpp index 97efdc161..f136086e8 100644 --- a/pybind11/tests/test_gil_scoped.cpp +++ b/pybind11/tests/test_gil_scoped.cpp @@ -11,6 +11,13 @@ #include "pybind11_tests.h" +#include +#include + +#define CROSS_MODULE(Function) \ + auto cm = py::module_::import("cross_module_gil_utils"); \ + auto target = reinterpret_cast(PyLong_AsVoidPtr(cm.attr(Function).ptr())); + class VirtClass { public: virtual ~VirtClass() = default; @@ -28,6 +35,16 @@ class PyVirtClass : public VirtClass { }; TEST_SUBMODULE(gil_scoped, m) { + m.attr("defined_THREAD_SANITIZER") = +#if defined(THREAD_SANITIZER) + true; +#else + false; +#endif + + m.def("intentional_deadlock", + []() { std::thread([]() { py::gil_scoped_acquire gil_acquired; }).join(); }); + py::class_(m, "VirtClass") .def(py::init<>()) .def("virtual_func", &VirtClass::virtual_func) @@ -37,11 +54,91 @@ TEST_SUBMODULE(gil_scoped, m) { m.def("test_callback_std_func", [](const std::function &func) { func(); }); m.def("test_callback_virtual_func", [](VirtClass &virt) { virt.virtual_func(); }); m.def("test_callback_pure_virtual_func", [](VirtClass &virt) { virt.pure_virtual_func(); }); - m.def("test_cross_module_gil", []() { - auto cm = py::module_::import("cross_module_gil_utils"); - auto gil_acquire = reinterpret_cast( - PyLong_AsVoidPtr(cm.attr("gil_acquire_funcaddr").ptr())); + m.def("test_cross_module_gil_released", []() { + CROSS_MODULE("gil_acquire_funcaddr") py::gil_scoped_release gil_release; - gil_acquire(); + target(); + }); + m.def("test_cross_module_gil_acquired", []() { + CROSS_MODULE("gil_acquire_funcaddr") + py::gil_scoped_acquire gil_acquire; + target(); + }); + m.def("test_cross_module_gil_inner_custom_released", []() { + CROSS_MODULE("gil_acquire_inner_custom_funcaddr") + py::gil_scoped_release gil_release; + target(); + }); + m.def("test_cross_module_gil_inner_custom_acquired", []() { + CROSS_MODULE("gil_acquire_inner_custom_funcaddr") + py::gil_scoped_acquire gil_acquire; + target(); + }); + m.def("test_cross_module_gil_inner_pybind11_released", []() { + CROSS_MODULE("gil_acquire_inner_pybind11_funcaddr") + py::gil_scoped_release gil_release; + target(); + }); + m.def("test_cross_module_gil_inner_pybind11_acquired", []() { + CROSS_MODULE("gil_acquire_inner_pybind11_funcaddr") + py::gil_scoped_acquire gil_acquire; + target(); + }); + m.def("test_cross_module_gil_nested_custom_released", []() { + CROSS_MODULE("gil_acquire_nested_custom_funcaddr") + py::gil_scoped_release gil_release; + target(); + }); + m.def("test_cross_module_gil_nested_custom_acquired", []() { + CROSS_MODULE("gil_acquire_nested_custom_funcaddr") + py::gil_scoped_acquire gil_acquire; + target(); + }); + m.def("test_cross_module_gil_nested_pybind11_released", []() { + CROSS_MODULE("gil_acquire_nested_pybind11_funcaddr") + py::gil_scoped_release gil_release; + target(); + }); + m.def("test_cross_module_gil_nested_pybind11_acquired", []() { + CROSS_MODULE("gil_acquire_nested_pybind11_funcaddr") + py::gil_scoped_acquire gil_acquire; + target(); + }); + m.def("test_release_acquire", [](const py::object &obj) { + py::gil_scoped_release gil_released; + py::gil_scoped_acquire gil_acquired; + return py::str(obj); + }); + m.def("test_nested_acquire", [](const py::object &obj) { + py::gil_scoped_release gil_released; + py::gil_scoped_acquire gil_acquired_outer; + py::gil_scoped_acquire gil_acquired_inner; + return py::str(obj); + }); + m.def("test_multi_acquire_release_cross_module", [](unsigned bits) { + py::set internals_ids; + internals_ids.add(PYBIND11_INTERNALS_ID); + { + py::gil_scoped_release gil_released; + auto thread_f = [bits, &internals_ids]() { + py::gil_scoped_acquire gil_acquired; + auto cm = py::module_::import("cross_module_gil_utils"); + auto target = reinterpret_cast( + PyLong_AsVoidPtr(cm.attr("gil_multi_acquire_release_funcaddr").ptr())); + std::string cm_internals_id = target(bits >> 3); + internals_ids.add(cm_internals_id); + }; + if ((bits & 0x1u) != 0u) { + thread_f(); + } + if ((bits & 0x2u) != 0u) { + std::thread non_python_thread(thread_f); + non_python_thread.join(); + } + if ((bits & 0x4u) != 0u) { + thread_f(); + } + } + return internals_ids; }); } diff --git a/pybind11/tests/test_gil_scoped.py b/pybind11/tests/test_gil_scoped.py index 52374b0cc..fc8af9b77 100644 --- a/pybind11/tests/test_gil_scoped.py +++ b/pybind11/tests/test_gil_scoped.py @@ -1,45 +1,197 @@ import multiprocessing +import sys import threading +import time +import pytest + +import env from pybind11_tests import gil_scoped as m +class ExtendedVirtClass(m.VirtClass): + def virtual_func(self): + pass + + def pure_virtual_func(self): + pass + + +def test_callback_py_obj(): + m.test_callback_py_obj(lambda: None) + + +def test_callback_std_func(): + m.test_callback_std_func(lambda: None) + + +def test_callback_virtual_func(): + extended = ExtendedVirtClass() + m.test_callback_virtual_func(extended) + + +def test_callback_pure_virtual_func(): + extended = ExtendedVirtClass() + m.test_callback_pure_virtual_func(extended) + + +def test_cross_module_gil_released(): + """Makes sure that the GIL can be acquired by another module from a GIL-released state.""" + m.test_cross_module_gil_released() # Should not raise a SIGSEGV + + +def test_cross_module_gil_acquired(): + """Makes sure that the GIL can be acquired by another module from a GIL-acquired state.""" + m.test_cross_module_gil_acquired() # Should not raise a SIGSEGV + + +def test_cross_module_gil_inner_custom_released(): + """Makes sure that the GIL can be acquired/released by another module + from a GIL-released state using custom locking logic.""" + m.test_cross_module_gil_inner_custom_released() + + +def test_cross_module_gil_inner_custom_acquired(): + """Makes sure that the GIL can be acquired/acquired by another module + from a GIL-acquired state using custom locking logic.""" + m.test_cross_module_gil_inner_custom_acquired() + + +def test_cross_module_gil_inner_pybind11_released(): + """Makes sure that the GIL can be acquired/released by another module + from a GIL-released state using pybind11 locking logic.""" + m.test_cross_module_gil_inner_pybind11_released() + + +def test_cross_module_gil_inner_pybind11_acquired(): + """Makes sure that the GIL can be acquired/acquired by another module + from a GIL-acquired state using pybind11 locking logic.""" + m.test_cross_module_gil_inner_pybind11_acquired() + + +def test_cross_module_gil_nested_custom_released(): + """Makes sure that the GIL can be nested acquired/released by another module + from a GIL-released state using custom locking logic.""" + m.test_cross_module_gil_nested_custom_released() + + +def test_cross_module_gil_nested_custom_acquired(): + """Makes sure that the GIL can be nested acquired/acquired by another module + from a GIL-acquired state using custom locking logic.""" + m.test_cross_module_gil_nested_custom_acquired() + + +def test_cross_module_gil_nested_pybind11_released(): + """Makes sure that the GIL can be nested acquired/released by another module + from a GIL-released state using pybind11 locking logic.""" + m.test_cross_module_gil_nested_pybind11_released() + + +def test_cross_module_gil_nested_pybind11_acquired(): + """Makes sure that the GIL can be nested acquired/acquired by another module + from a GIL-acquired state using pybind11 locking logic.""" + m.test_cross_module_gil_nested_pybind11_acquired() + + +def test_release_acquire(): + assert m.test_release_acquire(0xAB) == "171" + + +def test_nested_acquire(): + assert m.test_nested_acquire(0xAB) == "171" + + +def test_multi_acquire_release_cross_module(): + for bits in range(16 * 8): + internals_ids = m.test_multi_acquire_release_cross_module(bits) + assert len(internals_ids) == 2 if bits % 8 else 1 + + +# Intentionally putting human review in the loop here, to guard against accidents. +VARS_BEFORE_ALL_BASIC_TESTS = dict(vars()) # Make a copy of the dict (critical). +ALL_BASIC_TESTS = ( + test_callback_py_obj, + test_callback_std_func, + test_callback_virtual_func, + test_callback_pure_virtual_func, + test_cross_module_gil_released, + test_cross_module_gil_acquired, + test_cross_module_gil_inner_custom_released, + test_cross_module_gil_inner_custom_acquired, + test_cross_module_gil_inner_pybind11_released, + test_cross_module_gil_inner_pybind11_acquired, + test_cross_module_gil_nested_custom_released, + test_cross_module_gil_nested_custom_acquired, + test_cross_module_gil_nested_pybind11_released, + test_cross_module_gil_nested_pybind11_acquired, + test_release_acquire, + test_nested_acquire, + test_multi_acquire_release_cross_module, +) + + +def test_all_basic_tests_completeness(): + num_found = 0 + for key, value in VARS_BEFORE_ALL_BASIC_TESTS.items(): + if not key.startswith("test_"): + continue + assert value in ALL_BASIC_TESTS + num_found += 1 + assert len(ALL_BASIC_TESTS) == num_found + + +def _intentional_deadlock(): + m.intentional_deadlock() + + +ALL_BASIC_TESTS_PLUS_INTENTIONAL_DEADLOCK = ALL_BASIC_TESTS + (_intentional_deadlock,) + + def _run_in_process(target, *args, **kwargs): - """Runs target in process and returns its exitcode after 10s (None if still alive).""" + test_fn = target if len(args) == 0 else args[0] + # Do not need to wait much, 10s should be more than enough. + timeout = 0.1 if test_fn is _intentional_deadlock else 10 process = multiprocessing.Process(target=target, args=args, kwargs=kwargs) process.daemon = True try: + t_start = time.time() process.start() - # Do not need to wait much, 10s should be more than enough. - process.join(timeout=10) + if timeout >= 100: # For debugging. + print( + "\nprocess.pid STARTED", process.pid, (sys.argv, target, args, kwargs) + ) + print(f"COPY-PASTE-THIS: gdb {sys.argv[0]} -p {process.pid}", flush=True) + process.join(timeout=timeout) + if timeout >= 100: + print("\nprocess.pid JOINED", process.pid, flush=True) + t_delta = time.time() - t_start + if process.exitcode == 66 and m.defined_THREAD_SANITIZER: # Issue #2754 + # WOULD-BE-NICE-TO-HAVE: Check that the message below is actually in the output. + # Maybe this could work: + # https://gist.github.com/alexeygrigorev/01ce847f2e721b513b42ea4a6c96905e + pytest.skip( + "ThreadSanitizer: starting new threads after multi-threaded fork is not supported." + ) + elif test_fn is _intentional_deadlock: + assert process.exitcode is None + return 0 + + if process.exitcode is None: + assert t_delta > 0.9 * timeout + msg = "DEADLOCK, most likely, exactly what this test is meant to detect." + if env.PYPY and env.WIN: + pytest.skip(msg) + raise RuntimeError(msg) return process.exitcode finally: if process.is_alive(): process.terminate() -def _python_to_cpp_to_python(): - """Calls different C++ functions that come back to Python.""" - - class ExtendedVirtClass(m.VirtClass): - def virtual_func(self): - pass - - def pure_virtual_func(self): - pass - - extended = ExtendedVirtClass() - m.test_callback_py_obj(lambda: None) - m.test_callback_std_func(lambda: None) - m.test_callback_virtual_func(extended) - m.test_callback_pure_virtual_func(extended) - - -def _python_to_cpp_to_python_from_threads(num_threads, parallel=False): - """Calls different C++ functions that come back to Python, from Python threads.""" +def _run_in_threads(test_fn, num_threads, parallel): threads = [] for _ in range(num_threads): - thread = threading.Thread(target=_python_to_cpp_to_python) + thread = threading.Thread(target=test_fn) thread.daemon = True thread.start() if parallel: @@ -51,43 +203,40 @@ def _python_to_cpp_to_python_from_threads(num_threads, parallel=False): # TODO: FIXME, sometimes returns -11 (segfault) instead of 0 on macOS Python 3.9 -def test_python_to_cpp_to_python_from_thread(): +@pytest.mark.parametrize("test_fn", ALL_BASIC_TESTS_PLUS_INTENTIONAL_DEADLOCK) +def test_run_in_process_one_thread(test_fn): """Makes sure there is no GIL deadlock when running in a thread. It runs in a separate process to be able to stop and assert if it deadlocks. """ - assert _run_in_process(_python_to_cpp_to_python_from_threads, 1) == 0 + assert _run_in_process(_run_in_threads, test_fn, num_threads=1, parallel=False) == 0 # TODO: FIXME on macOS Python 3.9 -def test_python_to_cpp_to_python_from_thread_multiple_parallel(): +@pytest.mark.parametrize("test_fn", ALL_BASIC_TESTS_PLUS_INTENTIONAL_DEADLOCK) +def test_run_in_process_multiple_threads_parallel(test_fn): """Makes sure there is no GIL deadlock when running in a thread multiple times in parallel. It runs in a separate process to be able to stop and assert if it deadlocks. """ - assert _run_in_process(_python_to_cpp_to_python_from_threads, 8, parallel=True) == 0 + assert _run_in_process(_run_in_threads, test_fn, num_threads=8, parallel=True) == 0 # TODO: FIXME on macOS Python 3.9 -def test_python_to_cpp_to_python_from_thread_multiple_sequential(): +@pytest.mark.parametrize("test_fn", ALL_BASIC_TESTS_PLUS_INTENTIONAL_DEADLOCK) +def test_run_in_process_multiple_threads_sequential(test_fn): """Makes sure there is no GIL deadlock when running in a thread multiple times sequentially. It runs in a separate process to be able to stop and assert if it deadlocks. """ - assert ( - _run_in_process(_python_to_cpp_to_python_from_threads, 8, parallel=False) == 0 - ) + assert _run_in_process(_run_in_threads, test_fn, num_threads=8, parallel=False) == 0 # TODO: FIXME on macOS Python 3.9 -def test_python_to_cpp_to_python_from_process(): +@pytest.mark.parametrize("test_fn", ALL_BASIC_TESTS_PLUS_INTENTIONAL_DEADLOCK) +def test_run_in_process_direct(test_fn): """Makes sure there is no GIL deadlock when using processes. This test is for completion, but it was never an issue. """ - assert _run_in_process(_python_to_cpp_to_python) == 0 - - -def test_cross_module_gil(): - """Makes sure that the GIL can be acquired by another module from a GIL-released state.""" - m.test_cross_module_gil() # Should not raise a SIGSEGV + assert _run_in_process(test_fn) == 0 diff --git a/pybind11/tests/test_iostream.py b/pybind11/tests/test_iostream.py index 5bbdf6955..d283eb152 100644 --- a/pybind11/tests/test_iostream.py +++ b/pybind11/tests/test_iostream.py @@ -9,16 +9,16 @@ def test_captured(capsys): m.captured_output(msg) stdout, stderr = capsys.readouterr() assert stdout == msg - assert stderr == "" + assert not stderr m.captured_output_default(msg) stdout, stderr = capsys.readouterr() assert stdout == msg - assert stderr == "" + assert not stderr m.captured_err(msg) stdout, stderr = capsys.readouterr() - assert stdout == "" + assert not stdout assert stderr == msg @@ -30,7 +30,7 @@ def test_captured_large_string(capsys): m.captured_output_default(msg) stdout, stderr = capsys.readouterr() assert stdout == msg - assert stderr == "" + assert not stderr def test_captured_utf8_2byte_offset0(capsys): @@ -40,7 +40,7 @@ def test_captured_utf8_2byte_offset0(capsys): m.captured_output_default(msg) stdout, stderr = capsys.readouterr() assert stdout == msg - assert stderr == "" + assert not stderr def test_captured_utf8_2byte_offset1(capsys): @@ -50,7 +50,7 @@ def test_captured_utf8_2byte_offset1(capsys): m.captured_output_default(msg) stdout, stderr = capsys.readouterr() assert stdout == msg - assert stderr == "" + assert not stderr def test_captured_utf8_3byte_offset0(capsys): @@ -60,7 +60,7 @@ def test_captured_utf8_3byte_offset0(capsys): m.captured_output_default(msg) stdout, stderr = capsys.readouterr() assert stdout == msg - assert stderr == "" + assert not stderr def test_captured_utf8_3byte_offset1(capsys): @@ -70,7 +70,7 @@ def test_captured_utf8_3byte_offset1(capsys): m.captured_output_default(msg) stdout, stderr = capsys.readouterr() assert stdout == msg - assert stderr == "" + assert not stderr def test_captured_utf8_3byte_offset2(capsys): @@ -80,7 +80,7 @@ def test_captured_utf8_3byte_offset2(capsys): m.captured_output_default(msg) stdout, stderr = capsys.readouterr() assert stdout == msg - assert stderr == "" + assert not stderr def test_captured_utf8_4byte_offset0(capsys): @@ -90,7 +90,7 @@ def test_captured_utf8_4byte_offset0(capsys): m.captured_output_default(msg) stdout, stderr = capsys.readouterr() assert stdout == msg - assert stderr == "" + assert not stderr def test_captured_utf8_4byte_offset1(capsys): @@ -100,7 +100,7 @@ def test_captured_utf8_4byte_offset1(capsys): m.captured_output_default(msg) stdout, stderr = capsys.readouterr() assert stdout == msg - assert stderr == "" + assert not stderr def test_captured_utf8_4byte_offset2(capsys): @@ -110,7 +110,7 @@ def test_captured_utf8_4byte_offset2(capsys): m.captured_output_default(msg) stdout, stderr = capsys.readouterr() assert stdout == msg - assert stderr == "" + assert not stderr def test_captured_utf8_4byte_offset3(capsys): @@ -120,7 +120,7 @@ def test_captured_utf8_4byte_offset3(capsys): m.captured_output_default(msg) stdout, stderr = capsys.readouterr() assert stdout == msg - assert stderr == "" + assert not stderr def test_guard_capture(capsys): @@ -128,7 +128,7 @@ def test_guard_capture(capsys): m.guard_output(msg) stdout, stderr = capsys.readouterr() assert stdout == msg - assert stderr == "" + assert not stderr def test_series_captured(capture): @@ -145,7 +145,7 @@ def test_flush(capfd): with m.ostream_redirect(): m.noisy_function(msg, flush=False) stdout, stderr = capfd.readouterr() - assert stdout == "" + assert not stdout m.noisy_function(msg2, flush=True) stdout, stderr = capfd.readouterr() @@ -164,15 +164,15 @@ def test_not_captured(capfd): m.raw_output(msg) stdout, stderr = capfd.readouterr() assert stdout == msg - assert stderr == "" - assert stream.getvalue() == "" + assert not stderr + assert not stream.getvalue() stream = StringIO() with redirect_stdout(stream): m.captured_output(msg) stdout, stderr = capfd.readouterr() - assert stdout == "" - assert stderr == "" + assert not stdout + assert not stderr assert stream.getvalue() == msg @@ -182,16 +182,16 @@ def test_err(capfd): with redirect_stderr(stream): m.raw_err(msg) stdout, stderr = capfd.readouterr() - assert stdout == "" + assert not stdout assert stderr == msg - assert stream.getvalue() == "" + assert not stream.getvalue() stream = StringIO() with redirect_stderr(stream): m.captured_err(msg) stdout, stderr = capfd.readouterr() - assert stdout == "" - assert stderr == "" + assert not stdout + assert not stderr assert stream.getvalue() == msg @@ -221,14 +221,13 @@ def test_redirect(capfd): m.raw_output(msg) stdout, stderr = capfd.readouterr() assert stdout == msg - assert stream.getvalue() == "" + assert not stream.getvalue() stream = StringIO() - with redirect_stdout(stream): - with m.ostream_redirect(): - m.raw_output(msg) + with redirect_stdout(stream), m.ostream_redirect(): + m.raw_output(msg) stdout, stderr = capfd.readouterr() - assert stdout == "" + assert not stdout assert stream.getvalue() == msg stream = StringIO() @@ -236,7 +235,7 @@ def test_redirect(capfd): m.raw_output(msg) stdout, stderr = capfd.readouterr() assert stdout == msg - assert stream.getvalue() == "" + assert not stream.getvalue() def test_redirect_err(capfd): @@ -244,13 +243,12 @@ def test_redirect_err(capfd): msg2 = "StdErr" stream = StringIO() - with redirect_stderr(stream): - with m.ostream_redirect(stdout=False): - m.raw_output(msg) - m.raw_err(msg2) + with redirect_stderr(stream), m.ostream_redirect(stdout=False): + m.raw_output(msg) + m.raw_err(msg2) stdout, stderr = capfd.readouterr() assert stdout == msg - assert stderr == "" + assert not stderr assert stream.getvalue() == msg2 @@ -260,14 +258,12 @@ def test_redirect_both(capfd): stream = StringIO() stream2 = StringIO() - with redirect_stdout(stream): - with redirect_stderr(stream2): - with m.ostream_redirect(): - m.raw_output(msg) - m.raw_err(msg2) + with redirect_stdout(stream), redirect_stderr(stream2), m.ostream_redirect(): + m.raw_output(msg) + m.raw_err(msg2) stdout, stderr = capfd.readouterr() - assert stdout == "" - assert stderr == "" + assert not stdout + assert not stderr assert stream.getvalue() == msg assert stream2.getvalue() == msg2 diff --git a/pybind11/tests/test_kwargs_and_defaults.cpp b/pybind11/tests/test_kwargs_and_defaults.cpp index 7418afefb..77e72c0c7 100644 --- a/pybind11/tests/test_kwargs_and_defaults.cpp +++ b/pybind11/tests/test_kwargs_and_defaults.cpp @@ -43,7 +43,15 @@ TEST_SUBMODULE(kwargs_and_defaults, m) { m.def("kw_func_udl_z", kw_func, "x"_a, "y"_a = 0); // test_args_and_kwargs - m.def("args_function", [](py::args args) -> py::tuple { return std::move(args); }); + m.def("args_function", [](py::args args) -> py::tuple { + PYBIND11_WARNING_PUSH + +#ifdef PYBIND11_DETECTED_CLANG_WITH_MISLEADING_CALL_STD_MOVE_EXPLICITLY_WARNING + PYBIND11_WARNING_DISABLE_CLANG("-Wreturn-std-move") +#endif + return args; + PYBIND11_WARNING_POP + }); m.def("args_kwargs_function", [](const py::args &args, const py::kwargs &kwargs) { return py::make_tuple(args, kwargs); }); diff --git a/pybind11/tests/test_kwargs_and_defaults.py b/pybind11/tests/test_kwargs_and_defaults.py index ab7017886..7174726fc 100644 --- a/pybind11/tests/test_kwargs_and_defaults.py +++ b/pybind11/tests/test_kwargs_and_defaults.py @@ -25,7 +25,7 @@ def test_function_signatures(doc): ) -def test_named_arguments(msg): +def test_named_arguments(): assert m.kw_func0(5, 10) == "x=5, y=10" assert m.kw_func1(5, 10) == "x=5, y=10" @@ -43,8 +43,7 @@ def test_named_arguments(msg): # noinspection PyArgumentList m.kw_func2(x=5, y=10, z=12) assert excinfo.match( - r"(?s)^kw_func2\(\): incompatible.*Invoked with: kwargs: ((x=5|y=10|z=12)(, |$))" - + "{3}$" + r"(?s)^kw_func2\(\): incompatible.*Invoked with: kwargs: ((x=5|y=10|z=12)(, |$)){3}$" ) assert m.kw_func4() == "{13 17}" @@ -59,7 +58,7 @@ def test_arg_and_kwargs(): assert m.args_function(*args) == args args = "a1", "a2" - kwargs = dict(arg3="a3", arg4=4) + kwargs = {"arg3": "a3", "arg4": 4} assert m.args_kwargs_function(*args, **kwargs) == (args, kwargs) @@ -177,7 +176,7 @@ def test_mixed_args_and_kwargs(msg): assert ( m.args_kwonly_kwargs_defaults.__doc__ - == "args_kwonly_kwargs_defaults(i: int = 1, j: float = 3.14159, *args, z: int = 42, **kwargs) -> tuple\n" # noqa: E501 line too long + == "args_kwonly_kwargs_defaults(i: int = 1, j: float = 3.14159, *args, z: int = 42, **kwargs) -> tuple\n" ) assert m.args_kwonly_kwargs_defaults() == (1, 3.14159, (), 42, {}) assert m.args_kwonly_kwargs_defaults(2) == (2, 3.14159, (), 42, {}) @@ -233,15 +232,15 @@ def test_keyword_only_args(msg): x.method(i=1, j=2) assert ( m.first_arg_kw_only.__init__.__doc__ - == "__init__(self: pybind11_tests.kwargs_and_defaults.first_arg_kw_only, *, i: int = 0) -> None\n" # noqa: E501 line too long + == "__init__(self: pybind11_tests.kwargs_and_defaults.first_arg_kw_only, *, i: int = 0) -> None\n" ) assert ( m.first_arg_kw_only.method.__doc__ - == "method(self: pybind11_tests.kwargs_and_defaults.first_arg_kw_only, *, i: int = 1, j: int = 2) -> None\n" # noqa: E501 line too long + == "method(self: pybind11_tests.kwargs_and_defaults.first_arg_kw_only, *, i: int = 1, j: int = 2) -> None\n" ) -def test_positional_only_args(msg): +def test_positional_only_args(): assert m.pos_only_all(1, 2) == (1, 2) assert m.pos_only_all(2, 1) == (2, 1) @@ -283,7 +282,7 @@ def test_positional_only_args(msg): # Mix it with args and kwargs: assert ( m.args_kwonly_full_monty.__doc__ - == "args_kwonly_full_monty(arg0: int = 1, arg1: int = 2, /, j: float = 3.14159, *args, z: int = 42, **kwargs) -> tuple\n" # noqa: E501 line too long + == "args_kwonly_full_monty(arg0: int = 1, arg1: int = 2, /, j: float = 3.14159, *args, z: int = 42, **kwargs) -> tuple\n" ) assert m.args_kwonly_full_monty() == (1, 2, 3.14159, (), 42, {}) assert m.args_kwonly_full_monty(8) == (8, 2, 3.14159, (), 42, {}) @@ -326,18 +325,18 @@ def test_positional_only_args(msg): # https://github.com/pybind/pybind11/pull/3402#issuecomment-963341987 assert ( m.first_arg_kw_only.pos_only.__doc__ - == "pos_only(self: pybind11_tests.kwargs_and_defaults.first_arg_kw_only, /, i: int, j: int) -> None\n" # noqa: E501 line too long + == "pos_only(self: pybind11_tests.kwargs_and_defaults.first_arg_kw_only, /, i: int, j: int) -> None\n" ) def test_signatures(): - assert "kw_only_all(*, i: int, j: int) -> tuple\n" == m.kw_only_all.__doc__ - assert "kw_only_mixed(i: int, *, j: int) -> tuple\n" == m.kw_only_mixed.__doc__ - assert "pos_only_all(i: int, j: int, /) -> tuple\n" == m.pos_only_all.__doc__ - assert "pos_only_mix(i: int, /, j: int) -> tuple\n" == m.pos_only_mix.__doc__ + assert m.kw_only_all.__doc__ == "kw_only_all(*, i: int, j: int) -> tuple\n" + assert m.kw_only_mixed.__doc__ == "kw_only_mixed(i: int, *, j: int) -> tuple\n" + assert m.pos_only_all.__doc__ == "pos_only_all(i: int, j: int, /) -> tuple\n" + assert m.pos_only_mix.__doc__ == "pos_only_mix(i: int, /, j: int) -> tuple\n" assert ( - "pos_kw_only_mix(i: int, /, j: int, *, k: int) -> tuple\n" - == m.pos_kw_only_mix.__doc__ + m.pos_kw_only_mix.__doc__ + == "pos_kw_only_mix(i: int, /, j: int, *, k: int) -> tuple\n" ) diff --git a/pybind11/tests/test_local_bindings.py b/pybind11/tests/test_local_bindings.py index 654d96d49..d64187739 100644 --- a/pybind11/tests/test_local_bindings.py +++ b/pybind11/tests/test_local_bindings.py @@ -130,7 +130,8 @@ def test_stl_bind_global(): def test_mixed_local_global(): """Local types take precedence over globally registered types: a module with a `module_local` type can be registered even if the type is already registered globally. With the module, - casting will go to the local type; outside the module casting goes to the global type.""" + casting will go to the local type; outside the module casting goes to the global type. + """ import pybind11_cross_module_tests as cm m.register_mixed_global() diff --git a/pybind11/tests/test_methods_and_attributes.cpp b/pybind11/tests/test_methods_and_attributes.cpp index 815dd5e98..31d46eb7e 100644 --- a/pybind11/tests/test_methods_and_attributes.cpp +++ b/pybind11/tests/test_methods_and_attributes.cpp @@ -177,6 +177,38 @@ struct RValueRefParam { std::size_t func4(std::string &&s) const & { return s.size(); } }; +namespace pybind11_tests { +namespace exercise_is_setter { + +struct FieldBase { + int int_value() const { return int_value_; } + + FieldBase &SetIntValue(int int_value) { + int_value_ = int_value; + return *this; + } + +private: + int int_value_ = -99; +}; + +struct Field : FieldBase {}; + +void add_bindings(py::module &m) { + py::module sm = m.def_submodule("exercise_is_setter"); + // NOTE: FieldBase is not wrapped, therefore ... + py::class_(sm, "Field") + .def(py::init<>()) + .def_property( + "int_value", + &Field::int_value, + &Field::SetIntValue // ... the `FieldBase &` return value here cannot be converted. + ); +} + +} // namespace exercise_is_setter +} // namespace pybind11_tests + TEST_SUBMODULE(methods_and_attributes, m) { // test_methods_and_attributes py::class_ emna(m, "ExampleMandA"); @@ -456,4 +488,6 @@ TEST_SUBMODULE(methods_and_attributes, m) { .def("func2", &RValueRefParam::func2) .def("func3", &RValueRefParam::func3) .def("func4", &RValueRefParam::func4); + + pybind11_tests::exercise_is_setter::add_bindings(m); } diff --git a/pybind11/tests/test_methods_and_attributes.py b/pybind11/tests/test_methods_and_attributes.py index 0a2ae1239..955a85f67 100644 --- a/pybind11/tests/test_methods_and_attributes.py +++ b/pybind11/tests/test_methods_and_attributes.py @@ -183,9 +183,9 @@ def test_static_properties(): # Only static attributes can be deleted del m.TestPropertiesOverride.def_readonly_static + assert hasattr(m.TestPropertiesOverride, "def_readonly_static") assert ( - hasattr(m.TestPropertiesOverride, "def_readonly_static") - and m.TestPropertiesOverride.def_readonly_static + m.TestPropertiesOverride.def_readonly_static is m.TestProperties.def_readonly_static ) assert "def_readonly_static" not in m.TestPropertiesOverride.__dict__ @@ -256,10 +256,7 @@ def test_no_mixed_overloads(): @pytest.mark.parametrize("access", ["ro", "rw", "static_ro", "static_rw"]) def test_property_return_value_policies(access): - if not access.startswith("static"): - obj = m.TestPropRVP() - else: - obj = m.TestPropRVP + obj = m.TestPropRVP() if not access.startswith("static") else m.TestPropRVP ref = getattr(obj, access + "_ref") assert ref.value == 1 @@ -525,3 +522,12 @@ def test_rvalue_ref_param(): assert r.func2("1234") == 4 assert r.func3("12345") == 5 assert r.func4("123456") == 6 + + +def test_is_setter(): + fld = m.exercise_is_setter.Field() + assert fld.int_value == -99 + setter_return = fld.int_value = 100 + assert isinstance(setter_return, int) + assert setter_return == 100 + assert fld.int_value == 100 diff --git a/pybind11/tests/test_modules.py b/pybind11/tests/test_modules.py index e11d68e78..2f6d825b7 100644 --- a/pybind11/tests/test_modules.py +++ b/pybind11/tests/test_modules.py @@ -1,3 +1,5 @@ +import builtins + import pytest import env @@ -61,7 +63,6 @@ def test_importing(): from pybind11_tests.modules import OD assert OD is OrderedDict - assert str(OD([(1, "a"), (2, "b")])) == "OrderedDict([(1, 'a'), (2, 'b')])" def test_pydoc(): @@ -86,12 +87,7 @@ def test_builtin_key_type(): Previous versions of pybind11 would add a unicode key in python 2. """ - if hasattr(__builtins__, "keys"): - keys = __builtins__.keys() - else: # this is to make pypy happy since builtins is different there. - keys = __builtins__.__dict__.keys() - - assert {type(k) for k in keys} == {str} + assert all(type(k) == str for k in dir(builtins)) @pytest.mark.xfail("env.PYPY", reason="PyModule_GetName()") @@ -107,11 +103,10 @@ def test_def_submodule_failures(): sm_name_orig = sm.__name__ sm.__name__ = malformed_utf8 try: - with pytest.raises(Exception): - # Seen with Python 3.9: SystemError: nameless module - # But we do not want to exercise the internals of PyModule_GetName(), which could - # change in future versions of Python, but a bad __name__ is very likely to cause - # some kind of failure indefinitely. + # We want to assert that a bad __name__ causes some kind of failure, although we do not want to exercise + # the internals of PyModule_GetName(). Currently all supported Python versions raise SystemError. If that + # changes in future Python versions, simply add the new expected exception types here. + with pytest.raises(SystemError): m.def_submodule(sm, b"SubSubModuleName") finally: # Clean up to ensure nothing gets upset by a module with an invalid __name__. diff --git a/pybind11/tests/test_numpy_array.cpp b/pybind11/tests/test_numpy_array.cpp index 69ddbe1ef..8c122a865 100644 --- a/pybind11/tests/test_numpy_array.cpp +++ b/pybind11/tests/test_numpy_array.cpp @@ -521,4 +521,32 @@ TEST_SUBMODULE(numpy_array, sm) { sm.def("test_fmt_desc_double", [](const py::array_t &) {}); sm.def("test_fmt_desc_const_float", [](const py::array_t &) {}); sm.def("test_fmt_desc_const_double", [](const py::array_t &) {}); + + sm.def("round_trip_float", [](double d) { return d; }); + + sm.def("pass_array_pyobject_ptr_return_sum_str_values", + [](const py::array_t &objs) { + std::string sum_str_values; + for (const auto &obj : objs) { + sum_str_values += py::str(obj.attr("value")); + } + return sum_str_values; + }); + + sm.def("pass_array_pyobject_ptr_return_as_list", + [](const py::array_t &objs) -> py::list { return objs; }); + + sm.def("return_array_pyobject_ptr_cpp_loop", [](const py::list &objs) { + py::size_t arr_size = py::len(objs); + py::array_t arr_from_list(static_cast(arr_size)); + PyObject **data = arr_from_list.mutable_data(); + for (py::size_t i = 0; i < arr_size; i++) { + assert(data[i] == nullptr); + data[i] = py::cast(objs[i].attr("value")); + } + return arr_from_list; + }); + + sm.def("return_array_pyobject_ptr_from_list", + [](const py::list &objs) -> py::array_t { return objs; }); } diff --git a/pybind11/tests/test_numpy_array.py b/pybind11/tests/test_numpy_array.py index 504963b16..12e7d17d1 100644 --- a/pybind11/tests/test_numpy_array.py +++ b/pybind11/tests/test_numpy_array.py @@ -22,7 +22,7 @@ def test_dtypes(): ) -@pytest.fixture(scope="function") +@pytest.fixture() def arr(): return np.array([[1, 2, 3], [4, 5, 6]], "=u2") @@ -67,7 +67,7 @@ def test_array_attributes(): @pytest.mark.parametrize( - "args, ret", [([], 0), ([0], 0), ([1], 3), ([0, 1], 1), ([1, 2], 5)] + ("args", "ret"), [([], 0), ([0], 0), ([1], 3), ([0, 1], 1), ([1, 2], 5)] ) def test_index_offset(arr, args, ret): assert m.index_at(arr, *args) == ret @@ -93,7 +93,7 @@ def test_dim_check_fail(arr): @pytest.mark.parametrize( - "args, ret", + ("args", "ret"), [ ([], [1, 2, 3, 4, 5, 6]), ([1], [4, 5, 6]), @@ -211,12 +211,14 @@ def test_wrap(): assert b[0, 0] == 1234 a1 = np.array([1, 2], dtype=np.int16) - assert a1.flags.owndata and a1.base is None + assert a1.flags.owndata + assert a1.base is None a2 = m.wrap(a1) assert_references(a1, a2) a1 = np.array([[1, 2], [3, 4]], dtype=np.float32, order="F") - assert a1.flags.owndata and a1.base is None + assert a1.flags.owndata + assert a1.base is None a2 = m.wrap(a1) assert_references(a1, a2) @@ -451,13 +453,15 @@ def test_array_resize(): try: m.array_resize3(a, 3, True) except ValueError as e: - assert str(e).startswith("cannot resize an array") + assert str(e).startswith("cannot resize an array") # noqa: PT017 # transposed array doesn't own data b = a.transpose() try: m.array_resize3(b, 3, False) except ValueError as e: - assert str(e).startswith("cannot resize this array: it does not own its data") + assert str(e).startswith( # noqa: PT017 + "cannot resize this array: it does not own its data" + ) # ... but reshape should be fine m.array_reshape2(b) assert b.shape == (8, 8) @@ -585,3 +589,80 @@ def test_dtype_refcount_leak(): m.ndim(a) after = getrefcount(dtype) assert after == before + + +def test_round_trip_float(): + arr = np.zeros((), np.float64) + arr[()] = 37.2 + assert m.round_trip_float(arr) == 37.2 + + +# HINT: An easy and robust way (although only manual unfortunately) to check for +# ref-count leaks in the test_.*pyobject_ptr.* functions below is to +# * temporarily insert `while True:` (one-by-one), +# * run this test, and +# * run the Linux `top` command in another shell to visually monitor +# `RES` for a minute or two. +# If there is a leak, it is usually evident in seconds because the `RES` +# value increases without bounds. (Don't forget to Ctrl-C the test!) + + +# For use as a temporary user-defined object, to maximize sensitivity of the tests below: +# * Ref-count leaks will be immediately evident. +# * Sanitizers are much more likely to detect heap-use-after-free due to +# other ref-count bugs. +class PyValueHolder: + def __init__(self, value): + self.value = value + + +def WrapWithPyValueHolder(*values): + return [PyValueHolder(v) for v in values] + + +def UnwrapPyValueHolder(vhs): + return [vh.value for vh in vhs] + + +def test_pass_array_pyobject_ptr_return_sum_str_values_ndarray(): + # Intentionally all temporaries, do not change. + assert ( + m.pass_array_pyobject_ptr_return_sum_str_values( + np.array(WrapWithPyValueHolder(-3, "four", 5.0), dtype=object) + ) + == "-3four5.0" + ) + + +def test_pass_array_pyobject_ptr_return_sum_str_values_list(): + # Intentionally all temporaries, do not change. + assert ( + m.pass_array_pyobject_ptr_return_sum_str_values( + WrapWithPyValueHolder(2, "three", -4.0) + ) + == "2three-4.0" + ) + + +def test_pass_array_pyobject_ptr_return_as_list(): + # Intentionally all temporaries, do not change. + assert UnwrapPyValueHolder( + m.pass_array_pyobject_ptr_return_as_list( + np.array(WrapWithPyValueHolder(-1, "two", 3.0), dtype=object) + ) + ) == [-1, "two", 3.0] + + +@pytest.mark.parametrize( + ("return_array_pyobject_ptr", "unwrap"), + [ + (m.return_array_pyobject_ptr_cpp_loop, list), + (m.return_array_pyobject_ptr_from_list, UnwrapPyValueHolder), + ], +) +def test_return_array_pyobject_ptr_cpp_loop(return_array_pyobject_ptr, unwrap): + # Intentionally all temporaries, do not change. + arr_from_list = return_array_pyobject_ptr(WrapWithPyValueHolder(6, "seven", -8.0)) + assert isinstance(arr_from_list, np.ndarray) + assert arr_from_list.dtype == np.dtype("O") + assert unwrap(arr_from_list) == [6, "seven", -8.0] diff --git a/pybind11/tests/test_numpy_dtypes.py b/pybind11/tests/test_numpy_dtypes.py index fcfd587b1..d10457eeb 100644 --- a/pybind11/tests/test_numpy_dtypes.py +++ b/pybind11/tests/test_numpy_dtypes.py @@ -130,14 +130,10 @@ def test_dtype(simple_dtype): partial_nested_fmt(), "[('a','S3'),('b','S3')]", ( - "{{'names':['a','b','c','d']," - + "'formats':[('S4',(3,)),('" - + e - + "i4',(2,)),('u1',(3,)),('" - + e - + "f4',(4,2))]," - + "'offsets':[0,12,20,24],'itemsize':56}}" - ).format(e=e), + "{'names':['a','b','c','d']," + f"'formats':[('S4',(3,)),('{e}i4',(2,)),('u1',(3,)),('{e}f4',(4,2))]," + "'offsets':[0,12,20,24],'itemsize':56}" + ), "[('e1','" + e + "i8'),('e2','u1')]", "[('x','i1'),('y','" + e + "u8')]", "[('cflt','" + e + "c8'),('cdbl','" + e + "c16')]", @@ -291,19 +287,17 @@ def test_array_array(): arr = m.create_array_array(3) assert str(arr.dtype).replace(" ", "") == ( - "{{'names':['a','b','c','d']," - + "'formats':[('S4',(3,)),('" - + e - + "i4',(2,)),('u1',(3,)),('{e}f4',(4,2))]," - + "'offsets':[0,12,20,24],'itemsize':56}}" - ).format(e=e) + "{'names':['a','b','c','d']," + f"'formats':[('S4',(3,)),('{e}i4',(2,)),('u1',(3,)),('{e}f4',(4,2))]," + "'offsets':[0,12,20,24],'itemsize':56}" + ) assert m.print_array_array(arr) == [ "a={{A,B,C,D},{K,L,M,N},{U,V,W,X}},b={0,1}," - + "c={0,1,2},d={{0,1},{10,11},{20,21},{30,31}}", + "c={0,1,2},d={{0,1},{10,11},{20,21},{30,31}}", "a={{W,X,Y,Z},{G,H,I,J},{Q,R,S,T}},b={1000,1001}," - + "c={10,11,12},d={{100,101},{110,111},{120,121},{130,131}}", + "c={10,11,12},d={{100,101},{110,111},{120,121},{130,131}}", "a={{S,T,U,V},{C,D,E,F},{M,N,O,P}},b={2000,2001}," - + "c={20,21,22},d={{200,201},{210,211},{220,221},{230,231}}", + "c={20,21,22},d={{200,201},{210,211},{220,221},{230,231}}", ] assert arr["a"].tolist() == [ [b"ABCD", b"KLMN", b"UVWX"], diff --git a/pybind11/tests/test_numpy_vectorize.py b/pybind11/tests/test_numpy_vectorize.py index 7e8c015c4..f1e8b6254 100644 --- a/pybind11/tests/test_numpy_vectorize.py +++ b/pybind11/tests/test_numpy_vectorize.py @@ -149,7 +149,7 @@ def test_docs(doc): doc(m.vectorized_func) == """ vectorized_func(arg0: numpy.ndarray[numpy.int32], arg1: numpy.ndarray[numpy.float32], arg2: numpy.ndarray[numpy.float64]) -> object - """ # noqa: E501 line too long + """ ) diff --git a/pybind11/tests/test_operator_overloading.cpp b/pybind11/tests/test_operator_overloading.cpp index a4b895a89..112a363b4 100644 --- a/pybind11/tests/test_operator_overloading.cpp +++ b/pybind11/tests/test_operator_overloading.cpp @@ -132,22 +132,18 @@ struct hash { // Not a good abs function, but easy to test. std::string abs(const Vector2 &) { return "abs(Vector2)"; } -// MSVC & Intel warns about unknown pragmas, and warnings are errors. -#if !defined(_MSC_VER) && !defined(__INTEL_COMPILER) -# pragma GCC diagnostic push // clang 7.0.0 and Apple LLVM 10.0.1 introduce `-Wself-assign-overloaded` to // `-Wall`, which is used here for overloading (e.g. `py::self += py::self `). -// Here, we suppress the warning using `#pragma diagnostic`. +// Here, we suppress the warning // Taken from: https://github.com/RobotLocomotion/drake/commit/aaf84b46 // TODO(eric): This could be resolved using a function / functor (e.g. `py::self()`). -# if defined(__APPLE__) && defined(__clang__) -# if (__clang_major__ >= 10) -# pragma GCC diagnostic ignored "-Wself-assign-overloaded" -# endif -# elif defined(__clang__) -# if (__clang_major__ >= 7) -# pragma GCC diagnostic ignored "-Wself-assign-overloaded" -# endif +#if defined(__APPLE__) && defined(__clang__) +# if (__clang_major__ >= 10) +PYBIND11_WARNING_DISABLE_CLANG("-Wself-assign-overloaded") +# endif +#elif defined(__clang__) +# if (__clang_major__ >= 7) +PYBIND11_WARNING_DISABLE_CLANG("-Wself-assign-overloaded") # endif #endif @@ -283,6 +279,3 @@ TEST_SUBMODULE(operators, m) { m.def("get_unhashable_HashMe_set", []() { return std::unordered_set{{"one"}}; }); } -#if !defined(_MSC_VER) && !defined(__INTEL_COMPILER) -# pragma GCC diagnostic pop -#endif diff --git a/pybind11/tests/test_operator_overloading.py b/pybind11/tests/test_operator_overloading.py index b228da3cc..9fde305a0 100644 --- a/pybind11/tests/test_operator_overloading.py +++ b/pybind11/tests/test_operator_overloading.py @@ -130,7 +130,6 @@ def test_nested(): def test_overriding_eq_reset_hash(): - assert m.Comparable(15) is not m.Comparable(15) assert m.Comparable(15) == m.Comparable(15) diff --git a/pybind11/tests/test_pytypes.cpp b/pybind11/tests/test_pytypes.cpp index f532e2608..b4ee64289 100644 --- a/pybind11/tests/test_pytypes.cpp +++ b/pybind11/tests/test_pytypes.cpp @@ -99,6 +99,8 @@ void m_defs(py::module_ &m) { } // namespace handle_from_move_only_type_with_operator_PyObject TEST_SUBMODULE(pytypes, m) { + m.def("obj_class_name", [](py::handle obj) { return py::detail::obj_class_name(obj.ptr()); }); + handle_from_move_only_type_with_operator_PyObject::m_defs(m); // test_bool @@ -109,6 +111,11 @@ TEST_SUBMODULE(pytypes, m) { m.def("get_iterator", [] { return py::iterator(); }); // test_iterable m.def("get_iterable", [] { return py::iterable(); }); + m.def("get_frozenset_from_iterable", + [](const py::iterable &iter) { return py::frozenset(iter); }); + m.def("get_list_from_iterable", [](const py::iterable &iter) { return py::list(iter); }); + m.def("get_set_from_iterable", [](const py::iterable &iter) { return py::set(iter); }); + m.def("get_tuple_from_iterable", [](const py::iterable &iter) { return py::tuple(iter); }); // test_float m.def("get_float", [] { return py::float_(0.0f); }); // test_list @@ -178,7 +185,7 @@ TEST_SUBMODULE(pytypes, m) { return d2; }); m.def("dict_contains", - [](const py::dict &dict, py::object val) { return dict.contains(val); }); + [](const py::dict &dict, const py::object &val) { return dict.contains(val); }); m.def("dict_contains", [](const py::dict &dict, const char *val) { return dict.contains(val); }); @@ -201,7 +208,12 @@ TEST_SUBMODULE(pytypes, m) { m.def("str_from_char_ssize_t", []() { return py::str{"red", (py::ssize_t) 3}; }); m.def("str_from_char_size_t", []() { return py::str{"blue", (py::size_t) 4}; }); m.def("str_from_string", []() { return py::str(std::string("baz")); }); + m.def("str_from_std_string_input", [](const std::string &stri) { return py::str(stri); }); + m.def("str_from_cstr_input", [](const char *c_str) { return py::str(c_str); }); m.def("str_from_bytes", []() { return py::str(py::bytes("boo", 3)); }); + m.def("str_from_bytes_input", + [](const py::bytes &encoded_str) { return py::str(encoded_str); }); + m.def("str_from_object", [](const py::object &obj) { return py::str(obj); }); m.def("repr_from_object", [](const py::object &obj) { return py::repr(obj); }); m.def("str_from_handle", [](py::handle h) { return py::str(h); }); @@ -248,6 +260,15 @@ TEST_SUBMODULE(pytypes, m) { }); }); + m.def("return_capsule_with_destructor_3", []() { + py::print("creating capsule"); + auto cap = py::capsule((void *) 1233, "oname", [](void *ptr) { + py::print("destructing capsule: {}"_s.format((size_t) ptr)); + }); + py::print("original name: {}"_s.format(cap.name())); + return cap; + }); + m.def("return_renamed_capsule_with_destructor_2", []() { py::print("creating capsule"); auto cap = py::capsule((void *) 1234, [](void *ptr) { @@ -284,6 +305,12 @@ TEST_SUBMODULE(pytypes, m) { return capsule; }); + m.def("return_capsule_with_explicit_nullptr_dtor", []() { + py::print("creating capsule with explicit nullptr dtor"); + return py::capsule(reinterpret_cast(1234), + static_cast(nullptr)); // PR #4221 + }); + // test_accessors m.def("accessor_api", [](const py::object &o) { auto d = py::dict(); @@ -527,6 +554,9 @@ TEST_SUBMODULE(pytypes, m) { m.def("hash_function", [](py::object obj) { return py::hash(std::move(obj)); }); + m.def("obj_contains", + [](py::object &obj, const py::object &key) { return obj.contains(key); }); + m.def("test_number_protocol", [](const py::object &a, const py::object &b) { py::list l; l.append(a.equal(b)); @@ -756,4 +786,38 @@ TEST_SUBMODULE(pytypes, m) { } return o; }); + + // testing immutable object augmented assignment: #issue 3812 + m.def("inplace_append", [](py::object &a, const py::object &b) { + a += b; + return a; + }); + m.def("inplace_subtract", [](py::object &a, const py::object &b) { + a -= b; + return a; + }); + m.def("inplace_multiply", [](py::object &a, const py::object &b) { + a *= b; + return a; + }); + m.def("inplace_divide", [](py::object &a, const py::object &b) { + a /= b; + return a; + }); + m.def("inplace_or", [](py::object &a, const py::object &b) { + a |= b; + return a; + }); + m.def("inplace_and", [](py::object &a, const py::object &b) { + a &= b; + return a; + }); + m.def("inplace_lshift", [](py::object &a, const py::object &b) { + a <<= b; + return a; + }); + m.def("inplace_rshift", [](py::object &a, const py::object &b) { + a >>= b; + return a; + }); } diff --git a/pybind11/tests/test_pytypes.py b/pybind11/tests/test_pytypes.py index 7a0a8b4ab..eda7a20a9 100644 --- a/pybind11/tests/test_pytypes.py +++ b/pybind11/tests/test_pytypes.py @@ -9,7 +9,13 @@ from pybind11_tests import detailed_error_messages_enabled from pybind11_tests import pytypes as m -def test_handle_from_move_only_type_with_operator_PyObject(): # noqa: N802 +def test_obj_class_name(): + assert m.obj_class_name(None) == "NoneType" + assert m.obj_class_name(list) == "list" + assert m.obj_class_name([]) == "list" + + +def test_handle_from_move_only_type_with_operator_PyObject(): assert m.handle_from_move_only_type_with_operator_PyObject_ncnst() assert m.handle_from_move_only_type_with_operator_PyObject_const() @@ -26,6 +32,22 @@ def test_iterator(doc): assert doc(m.get_iterator) == "get_iterator() -> Iterator" +@pytest.mark.parametrize( + ("pytype", "from_iter_func"), + [ + (frozenset, m.get_frozenset_from_iterable), + (list, m.get_list_from_iterable), + (set, m.get_set_from_iterable), + (tuple, m.get_tuple_from_iterable), + ], +) +def test_from_iterable(pytype, from_iter_func): + my_iter = iter(range(10)) + s = from_iter_func(my_iter) + assert type(s) == pytype + assert s == pytype(range(10)) + + def test_iterable(doc): assert doc(m.get_iterable) == "get_iterable() -> Iterable" @@ -65,7 +87,7 @@ def test_list(capture, doc): assert doc(m.print_list) == "print_list(arg0: list) -> None" -def test_none(capture, doc): +def test_none(doc): assert doc(m.get_none) == "get_none() -> None" assert doc(m.print_none) == "print_none(arg0: None) -> None" @@ -152,6 +174,31 @@ def test_dict(capture, doc): assert m.dict_keyword_constructor() == {"x": 1, "y": 2, "z": 3} +class CustomContains: + d = {"key": None} + + def __contains__(self, m): + return m in self.d + + +@pytest.mark.parametrize( + ("arg", "func"), + [ + (set(), m.anyset_contains), + ({}, m.dict_contains), + (CustomContains(), m.obj_contains), + ], +) +@pytest.mark.xfail("env.PYPY and sys.pypy_version_info < (7, 3, 10)", strict=False) +def test_unhashable_exceptions(arg, func): + class Unhashable: + __hash__ = None + + with pytest.raises(TypeError) as exc_info: + func(arg, Unhashable()) + assert "unhashable type:" in str(exc_info.value) + + def test_tuple(): assert m.tuple_no_args() == () assert m.tuple_ssize_t() == () @@ -203,6 +250,20 @@ def test_str(doc): m.str_from_string_from_str(ucs_surrogates_str) +@pytest.mark.parametrize( + "func", + [ + m.str_from_bytes_input, + m.str_from_cstr_input, + m.str_from_std_string_input, + ], +) +def test_surrogate_pairs_unicode_error(func): + input_str = "\ud83d\ude4f".encode("utf-8", "surrogatepass") + with pytest.raises(UnicodeDecodeError): + func(input_str) + + def test_bytes(doc): assert m.bytes_from_char_ssize_t().decode() == "green" assert m.bytes_from_char_size_t().decode() == "purple" @@ -212,7 +273,7 @@ def test_bytes(doc): assert doc(m.bytes_from_str) == "bytes_from_str() -> bytes" -def test_bytearray(doc): +def test_bytearray(): assert m.bytearray_from_char_ssize_t().decode() == "$%" assert m.bytearray_from_char_size_t().decode() == "@$!" assert m.bytearray_from_string().decode() == "foo" @@ -258,6 +319,19 @@ def test_capsule(capture): """ ) + with capture: + a = m.return_capsule_with_destructor_3() + del a + pytest.gc_collect() + assert ( + capture.unordered + == """ + creating capsule + destructing capsule: 1233 + original name: oname + """ + ) + with capture: a = m.return_renamed_capsule_with_destructor_2() del a @@ -283,6 +357,17 @@ def test_capsule(capture): """ ) + with capture: + a = m.return_capsule_with_explicit_nullptr_dtor() + del a + pytest.gc_collect() + assert ( + capture.unordered + == """ + creating capsule with explicit nullptr dtor + """ + ) + def test_accessors(): class SubTestObject: @@ -313,7 +398,7 @@ def test_accessors(): assert d["implicit_list"] == [1, 2, 3] assert all(x in TestObject.__dict__ for x in d["implicit_dict"]) - assert m.tuple_accessor(tuple()) == (0, 1, 2) + assert m.tuple_accessor(()) == (0, 1, 2) d = m.accessor_assignment() assert d["get"] == 0 @@ -403,7 +488,7 @@ def test_pybind11_str_raw_str(): assert cvt({}) == "{}" assert cvt({3: 4}) == "{3: 4}" assert cvt(set()) == "set()" - assert cvt({3, 3}) == "{3}" + assert cvt({3}) == "{3}" valid_orig = "DZ" valid_utf8 = valid_orig.encode("utf-8") @@ -464,7 +549,7 @@ def test_print(capture): assert str(excinfo.value) == "Unable to convert call argument " + ( "'1' of type 'UnregisteredType' to Python object" if detailed_error_messages_enabled - else "to Python object (#define PYBIND11_DETAILED_ERROR_MESSAGES or compile in debug mode for details)" + else "'1' to Python object (#define PYBIND11_DETAILED_ERROR_MESSAGES or compile in debug mode for details)" ) @@ -521,7 +606,7 @@ def test_issue2361(): @pytest.mark.parametrize( - "method, args, fmt, expected_view", + ("method", "args", "fmt", "expected_view"), [ (m.test_memoryview_object, (b"red",), "B", b"red"), (m.test_memoryview_buffer_info, (b"green",), "B", b"green"), @@ -579,7 +664,7 @@ def test_memoryview_from_memory(): def test_builtin_functions(): - assert m.get_len([i for i in range(42)]) == 42 + assert m.get_len(list(range(42))) == 42 with pytest.raises(TypeError) as exc_info: m.get_len(i for i in range(42)) assert str(exc_info.value) in [ @@ -623,7 +708,7 @@ def test_pass_bytes_or_unicode_to_string_types(): @pytest.mark.parametrize( - "create_weakref, create_weakref_with_callback", + ("create_weakref", "create_weakref_with_callback"), [ (m.weakref_from_handle, m.weakref_from_handle_and_function), (m.weakref_from_object, m.weakref_from_object_and_function), @@ -638,7 +723,7 @@ def test_weakref(create_weakref, create_weakref_with_callback): callback_called = False - def callback(wr): + def callback(_): nonlocal callback_called callback_called = True @@ -658,7 +743,7 @@ def test_weakref(create_weakref, create_weakref_with_callback): @pytest.mark.parametrize( - "create_weakref, has_callback", + ("create_weakref", "has_callback"), [ (m.weakref_from_handle, False), (m.weakref_from_object, False), @@ -676,10 +761,7 @@ def test_weakref_err(create_weakref, has_callback): ob = C() # Should raise TypeError on CPython with pytest.raises(TypeError) if not env.PYPY else contextlib.nullcontext(): - if has_callback: - _ = create_weakref(ob, callback) - else: - _ = create_weakref(ob) + _ = create_weakref(ob, callback) if has_callback else create_weakref(ob) def test_cpp_iterators(): @@ -739,3 +821,78 @@ def test_populate_obj_str_attrs(): new_attrs = {k: v for k, v in new_o.__dict__.items() if not k.startswith("_")} assert all(isinstance(v, str) for v in new_attrs.values()) assert len(new_attrs) == pop + + +@pytest.mark.parametrize( + ("a", "b"), + [("foo", "bar"), (1, 2), (1.0, 2.0), (list(range(3)), list(range(3, 6)))], +) +def test_inplace_append(a, b): + expected = a + b + assert m.inplace_append(a, b) == expected + + +@pytest.mark.parametrize( + ("a", "b"), [(3, 2), (3.0, 2.0), (set(range(3)), set(range(2)))] +) +def test_inplace_subtract(a, b): + expected = a - b + assert m.inplace_subtract(a, b) == expected + + +@pytest.mark.parametrize(("a", "b"), [(3, 2), (3.0, 2.0), ([1], 3)]) +def test_inplace_multiply(a, b): + expected = a * b + assert m.inplace_multiply(a, b) == expected + + +@pytest.mark.parametrize(("a", "b"), [(6, 3), (6.0, 3.0)]) +def test_inplace_divide(a, b): + expected = a / b + assert m.inplace_divide(a, b) == expected + + +@pytest.mark.parametrize( + ("a", "b"), + [ + (False, True), + ( + set(), + { + 1, + }, + ), + ], +) +def test_inplace_or(a, b): + expected = a | b + assert m.inplace_or(a, b) == expected + + +@pytest.mark.parametrize( + ("a", "b"), + [ + (True, False), + ( + {1, 2, 3}, + { + 1, + }, + ), + ], +) +def test_inplace_and(a, b): + expected = a & b + assert m.inplace_and(a, b) == expected + + +@pytest.mark.parametrize(("a", "b"), [(8, 1), (-3, 2)]) +def test_inplace_lshift(a, b): + expected = a << b + assert m.inplace_lshift(a, b) == expected + + +@pytest.mark.parametrize(("a", "b"), [(8, 1), (-2, 2)]) +def test_inplace_rshift(a, b): + expected = a >> b + assert m.inplace_rshift(a, b) == expected diff --git a/pybind11/tests/test_sequences_and_iterators.cpp b/pybind11/tests/test_sequences_and_iterators.cpp index b867f49a2..1de65edbf 100644 --- a/pybind11/tests/test_sequences_and_iterators.cpp +++ b/pybind11/tests/test_sequences_and_iterators.cpp @@ -559,4 +559,23 @@ TEST_SUBMODULE(sequences_and_iterators, m) { []() { return py::make_iterator(list); }); m.def("make_iterator_2", []() { return py::make_iterator(list); }); + + // test_iterator on c arrays + // #4100: ensure lvalue required as increment operand + class CArrayHolder { + public: + CArrayHolder(double x, double y, double z) { + values[0] = x; + values[1] = y; + values[2] = z; + }; + double values[3]; + }; + + py::class_(m, "CArrayHolder") + .def(py::init()) + .def( + "__iter__", + [](const CArrayHolder &v) { return py::make_iterator(v.values, v.values + 3); }, + py::keep_alive<0, 1>()); } diff --git a/pybind11/tests/test_sequences_and_iterators.py b/pybind11/tests/test_sequences_and_iterators.py index 062e3b3d3..dc129f2bf 100644 --- a/pybind11/tests/test_sequences_and_iterators.py +++ b/pybind11/tests/test_sequences_and_iterators.py @@ -1,5 +1,5 @@ import pytest -from pytest import approx +from pytest import approx # noqa: PT013 from pybind11_tests import ConstructorStats from pybind11_tests import sequences_and_iterators as m @@ -103,7 +103,8 @@ def test_sequence(): assert "Sequence" in repr(s) assert len(s) == 5 - assert s[0] == 0 and s[3] == 0 + assert s[0] == 0 + assert s[3] == 0 assert 12.34 not in s s[0], s[3] = 12.34, 56.78 assert 12.34 in s @@ -241,3 +242,11 @@ def test_iterator_rvp(): assert list(m.make_iterator_1()) == [1, 2, 3] assert list(m.make_iterator_2()) == [1, 2, 3] assert not isinstance(m.make_iterator_1(), type(m.make_iterator_2())) + + +def test_carray_iterator(): + """#4100: Check for proper iterator overload with C-Arrays""" + args_gt = [float(i) for i in range(3)] + arr_h = m.CArrayHolder(*args_gt) + args = list(arr_h) + assert args_gt == args diff --git a/pybind11/tests/test_smart_ptr.cpp b/pybind11/tests/test_smart_ptr.cpp index 2c23a9a19..6d9efcedc 100644 --- a/pybind11/tests/test_smart_ptr.cpp +++ b/pybind11/tests/test_smart_ptr.cpp @@ -266,14 +266,14 @@ struct ElementList { // It is always possible to construct a ref from an Object* pointer without // possible inconsistencies, hence the 'true' argument at the end. // Make pybind11 aware of the non-standard getter member function -namespace pybind11 { +namespace PYBIND11_NAMESPACE { namespace detail { template struct holder_helper> { static const T *get(const ref &p) { return p.get_ptr(); } }; } // namespace detail -} // namespace pybind11 +} // namespace PYBIND11_NAMESPACE // Make pybind aware of the ref-counted wrapper type (s): PYBIND11_DECLARE_HOLDER_TYPE(T, ref, true); diff --git a/pybind11/tests/test_stl.cpp b/pybind11/tests/test_stl.cpp index 38d32fda9..d45465d68 100644 --- a/pybind11/tests/test_stl.cpp +++ b/pybind11/tests/test_stl.cpp @@ -23,7 +23,7 @@ #if defined(PYBIND11_TEST_BOOST) # include -namespace pybind11 { +namespace PYBIND11_NAMESPACE { namespace detail { template struct type_caster> : optional_caster> {}; @@ -31,7 +31,7 @@ struct type_caster> : optional_caster> {}; template <> struct type_caster : void_caster {}; } // namespace detail -} // namespace pybind11 +} // namespace PYBIND11_NAMESPACE #endif // Test with `std::variant` in C++17 mode, or with `boost::variant` in C++11/14 @@ -43,7 +43,7 @@ using std::variant; # define PYBIND11_TEST_VARIANT 1 using boost::variant; -namespace pybind11 { +namespace PYBIND11_NAMESPACE { namespace detail { template struct type_caster> : variant_caster> {}; @@ -56,7 +56,7 @@ struct visit_helper { } }; } // namespace detail -} // namespace pybind11 +} // namespace PYBIND11_NAMESPACE #endif PYBIND11_MAKE_OPAQUE(std::vector>); @@ -159,13 +159,13 @@ private: std::vector storage; }; -namespace pybind11 { +namespace PYBIND11_NAMESPACE { namespace detail { template struct type_caster> : optional_caster> {}; } // namespace detail -} // namespace pybind11 +} // namespace PYBIND11_NAMESPACE TEST_SUBMODULE(stl, m) { // test_vector @@ -176,9 +176,14 @@ TEST_SUBMODULE(stl, m) { m.def("load_bool_vector", [](const std::vector &v) { return v.at(0) == true && v.at(1) == false; }); // Unnumbered regression (caused by #936): pointers to stl containers aren't castable - static std::vector lvv{2}; m.def( - "cast_ptr_vector", []() { return &lvv; }, py::return_value_policy::reference); + "cast_ptr_vector", + []() { + // Using no-destructor idiom to side-step warnings from overzealous compilers. + static auto *v = new std::vector{2}; + return v; + }, + py::return_value_policy::reference); // test_deque m.def("cast_deque", []() { return std::deque{1}; }); @@ -237,6 +242,7 @@ TEST_SUBMODULE(stl, m) { lvn["b"].emplace_back(); // add a list lvn["b"].back().emplace_back(); // add an array lvn["b"].back().emplace_back(); // add another array + static std::vector lvv{2}; m.def("cast_lv_vector", []() -> const decltype(lvv) & { return lvv; }); m.def("cast_lv_array", []() -> const decltype(lva) & { return lva; }); m.def("cast_lv_map", []() -> const decltype(lvm) & { return lvm; }); diff --git a/pybind11/tests/test_stl.py b/pybind11/tests/test_stl.py index d30c38211..8a614f8b8 100644 --- a/pybind11/tests/test_stl.py +++ b/pybind11/tests/test_stl.py @@ -14,7 +14,7 @@ def test_vector(doc): assert m.cast_bool_vector() == [True, False] assert m.load_bool_vector([True, False]) - assert m.load_bool_vector(tuple([True, False])) + assert m.load_bool_vector((True, False)) assert doc(m.cast_vector) == "cast_vector() -> List[int]" assert doc(m.load_vector) == "load_vector(arg0: List[int]) -> bool" @@ -23,7 +23,7 @@ def test_vector(doc): assert m.cast_ptr_vector() == ["lvalue", "lvalue"] -def test_deque(doc): +def test_deque(): """std::deque <-> list""" lst = m.cast_deque() assert lst == [1] @@ -39,8 +39,11 @@ def test_array(doc): assert m.load_array(lst) assert m.load_array(tuple(lst)) - assert doc(m.cast_array) == "cast_array() -> List[int[2]]" - assert doc(m.load_array) == "load_array(arg0: List[int[2]]) -> bool" + assert doc(m.cast_array) == "cast_array() -> Annotated[List[int], FixedSize(2)]" + assert ( + doc(m.load_array) + == "load_array(arg0: Annotated[List[int], FixedSize(2)]) -> bool" + ) def test_valarray(doc): @@ -95,7 +98,8 @@ def test_recursive_casting(): # Issue #853 test case: z = m.cast_unique_ptr_vector() - assert z[0].value == 7 and z[1].value == 42 + assert z[0].value == 7 + assert z[1].value == 42 def test_move_out_container(): @@ -366,7 +370,7 @@ def test_issue_1561(): """check fix for issue #1561""" bar = m.Issue1561Outer() bar.list = [m.Issue1561Inner("bar")] - bar.list + assert bar.list assert bar.list[0].data == "bar" diff --git a/pybind11/tests/test_stl_binders.cpp b/pybind11/tests/test_stl_binders.cpp index ca9630bd1..1681760aa 100644 --- a/pybind11/tests/test_stl_binders.cpp +++ b/pybind11/tests/test_stl_binders.cpp @@ -70,6 +70,44 @@ NestMap *times_hundred(int n) { return m; } +/* + * Recursive data structures as test for issue #4623 + */ +struct RecursiveVector : std::vector { + using Parent = std::vector; + using Parent::Parent; +}; + +struct RecursiveMap : std::map { + using Parent = std::map; + using Parent::Parent; +}; + +/* + * Pybind11 does not catch more complicated recursion schemes, such as mutual + * recursion. + * In that case custom recursive_container_traits specializations need to be added, + * thus manually telling pybind11 about the recursion. + */ +struct MutuallyRecursiveContainerPairMV; +struct MutuallyRecursiveContainerPairVM; + +struct MutuallyRecursiveContainerPairMV : std::map {}; +struct MutuallyRecursiveContainerPairVM : std::vector {}; + +namespace pybind11 { +namespace detail { +template +struct recursive_container_traits { + using type_to_check_recursively = recursive_bottom; +}; +template +struct recursive_container_traits { + using type_to_check_recursively = recursive_bottom; +}; +} // namespace detail +} // namespace pybind11 + TEST_SUBMODULE(stl_binders, m) { // test_vector_int py::bind_vector>(m, "VectorInt", py::buffer_protocol()); @@ -129,6 +167,12 @@ TEST_SUBMODULE(stl_binders, m) { m, "VectorUndeclStruct", py::buffer_protocol()); }); + // Bind recursive container types + py::bind_vector(m, "RecursiveVector"); + py::bind_map(m, "RecursiveMap"); + py::bind_map(m, "MutuallyRecursiveContainerPairMV"); + py::bind_vector(m, "MutuallyRecursiveContainerPairVM"); + // The rest depends on numpy: try { py::module_::import("numpy"); diff --git a/pybind11/tests/test_stl_binders.py b/pybind11/tests/test_stl_binders.py index d5e9ccced..e002f5b67 100644 --- a/pybind11/tests/test_stl_binders.py +++ b/pybind11/tests/test_stl_binders.py @@ -186,9 +186,9 @@ def test_map_string_double(): um["ua"] = 1.1 um["ub"] = 2.6 - assert sorted(list(um)) == ["ua", "ub"] + assert sorted(um) == ["ua", "ub"] assert list(um.keys()) == list(um) - assert sorted(list(um.items())) == [("ua", 1.1), ("ub", 2.6)] + assert sorted(um.items()) == [("ua", 1.1), ("ub", 2.6)] assert list(zip(um.keys(), um.values())) == list(um.items()) assert "UnorderedMapStringDouble" in str(um) @@ -304,8 +304,52 @@ def test_map_delitem(): um["ua"] = 1.1 um["ub"] = 2.6 - assert sorted(list(um)) == ["ua", "ub"] - assert sorted(list(um.items())) == [("ua", 1.1), ("ub", 2.6)] + assert sorted(um) == ["ua", "ub"] + assert sorted(um.items()) == [("ua", 1.1), ("ub", 2.6)] del um["ua"] - assert sorted(list(um)) == ["ub"] - assert sorted(list(um.items())) == [("ub", 2.6)] + assert sorted(um) == ["ub"] + assert sorted(um.items()) == [("ub", 2.6)] + + +def test_map_view_types(): + map_string_double = m.MapStringDouble() + unordered_map_string_double = m.UnorderedMapStringDouble() + map_string_double_const = m.MapStringDoubleConst() + unordered_map_string_double_const = m.UnorderedMapStringDoubleConst() + + assert map_string_double.keys().__class__.__name__ == "KeysView[str]" + assert map_string_double.values().__class__.__name__ == "ValuesView[float]" + assert map_string_double.items().__class__.__name__ == "ItemsView[str, float]" + + keys_type = type(map_string_double.keys()) + assert type(unordered_map_string_double.keys()) is keys_type + assert type(map_string_double_const.keys()) is keys_type + assert type(unordered_map_string_double_const.keys()) is keys_type + + values_type = type(map_string_double.values()) + assert type(unordered_map_string_double.values()) is values_type + assert type(map_string_double_const.values()) is values_type + assert type(unordered_map_string_double_const.values()) is values_type + + items_type = type(map_string_double.items()) + assert type(unordered_map_string_double.items()) is items_type + assert type(map_string_double_const.items()) is items_type + assert type(unordered_map_string_double_const.items()) is items_type + + +def test_recursive_vector(): + recursive_vector = m.RecursiveVector() + recursive_vector.append(m.RecursiveVector()) + recursive_vector[0].append(m.RecursiveVector()) + recursive_vector[0].append(m.RecursiveVector()) + # Can't use len() since test_stl_binders.cpp does not include stl.h, + # so the necessary conversion is missing + assert recursive_vector[0].count(m.RecursiveVector()) == 2 + + +def test_recursive_map(): + recursive_map = m.RecursiveMap() + recursive_map[100] = m.RecursiveMap() + recursive_map[100][101] = m.RecursiveMap() + recursive_map[100][102] = m.RecursiveMap() + assert list(recursive_map[100].keys()) == [101, 102] diff --git a/pybind11/tests/test_tagbased_polymorphic.cpp b/pybind11/tests/test_tagbased_polymorphic.cpp index 2807e86ba..12ba6532f 100644 --- a/pybind11/tests/test_tagbased_polymorphic.cpp +++ b/pybind11/tests/test_tagbased_polymorphic.cpp @@ -117,7 +117,7 @@ std::string Animal::name_of_kind(Kind kind) { return raw_name; } -namespace pybind11 { +namespace PYBIND11_NAMESPACE { template struct polymorphic_type_hook::value>> { static const void *get(const itype *src, const std::type_info *&type) { @@ -125,7 +125,7 @@ struct polymorphic_type_hook(m, "Animal").def_readonly("name", &Animal::name); diff --git a/pybind11/tests/test_type_caster_pyobject_ptr.cpp b/pybind11/tests/test_type_caster_pyobject_ptr.cpp new file mode 100644 index 000000000..1667ea126 --- /dev/null +++ b/pybind11/tests/test_type_caster_pyobject_ptr.cpp @@ -0,0 +1,130 @@ +#include +#include +#include + +#include "pybind11_tests.h" + +#include +#include + +namespace { + +std::vector make_vector_pyobject_ptr(const py::object &ValueHolder) { + std::vector vec_obj; + for (int i = 1; i < 3; i++) { + vec_obj.push_back(ValueHolder(i * 93).release().ptr()); + } + // This vector now owns the refcounts. + return vec_obj; +} + +} // namespace + +TEST_SUBMODULE(type_caster_pyobject_ptr, m) { + m.def("cast_from_pyobject_ptr", []() { + PyObject *ptr = PyLong_FromLongLong(6758L); + return py::cast(ptr, py::return_value_policy::take_ownership); + }); + m.def("cast_handle_to_pyobject_ptr", [](py::handle obj) { + auto rc1 = obj.ref_count(); + auto *ptr = py::cast(obj); + auto rc2 = obj.ref_count(); + if (rc2 != rc1 + 1) { + return -1; + } + return 100 - py::reinterpret_steal(ptr).attr("value").cast(); + }); + m.def("cast_object_to_pyobject_ptr", [](py::object obj) { + py::handle hdl = obj; + auto rc1 = hdl.ref_count(); + auto *ptr = py::cast(std::move(obj)); + auto rc2 = hdl.ref_count(); + if (rc2 != rc1) { + return -1; + } + return 300 - py::reinterpret_steal(ptr).attr("value").cast(); + }); + m.def("cast_list_to_pyobject_ptr", [](py::list lst) { + // This is to cover types implicitly convertible to object. + py::handle hdl = lst; + auto rc1 = hdl.ref_count(); + auto *ptr = py::cast(std::move(lst)); + auto rc2 = hdl.ref_count(); + if (rc2 != rc1) { + return -1; + } + return 400 - static_cast(py::len(py::reinterpret_steal(ptr))); + }); + + m.def( + "return_pyobject_ptr", + []() { return PyLong_FromLongLong(2314L); }, + py::return_value_policy::take_ownership); + m.def("pass_pyobject_ptr", [](PyObject *ptr) { + return 200 - py::reinterpret_borrow(ptr).attr("value").cast(); + }); + + m.def("call_callback_with_object_return", + [](const std::function &cb, int value) { return cb(value); }); + m.def( + "call_callback_with_pyobject_ptr_return", + [](const std::function &cb, int value) { return cb(value); }, + py::return_value_policy::take_ownership); + m.def( + "call_callback_with_pyobject_ptr_arg", + [](const std::function &cb, py::handle obj) { return cb(obj.ptr()); }, + py::arg("cb"), // This triggers return_value_policy::automatic_reference + py::arg("obj")); + + m.def("cast_to_pyobject_ptr_nullptr", [](bool set_error) { + if (set_error) { + PyErr_SetString(PyExc_RuntimeError, "Reflective of healthy error handling."); + } + PyObject *ptr = nullptr; + py::cast(ptr); + }); + + m.def("cast_to_pyobject_ptr_non_nullptr_with_error_set", []() { + PyErr_SetString(PyExc_RuntimeError, "Reflective of unhealthy error handling."); + py::cast(Py_None); + }); + + m.def("pass_list_pyobject_ptr", [](const std::vector &vec_obj) { + int acc = 0; + for (const auto &ptr : vec_obj) { + acc = acc * 1000 + py::reinterpret_borrow(ptr).attr("value").cast(); + } + return acc; + }); + + m.def("return_list_pyobject_ptr_take_ownership", + make_vector_pyobject_ptr, + // Ownership is transferred one-by-one when the vector is converted to a Python list. + py::return_value_policy::take_ownership); + + m.def("return_list_pyobject_ptr_reference", + make_vector_pyobject_ptr, + // Ownership is not transferred. + py::return_value_policy::reference); + + m.def("dec_ref_each_pyobject_ptr", [](const std::vector &vec_obj) { + std::size_t i = 0; + for (; i < vec_obj.size(); i++) { + py::handle h(vec_obj[i]); + if (static_cast(h.ref_count()) < 2) { + break; // Something is badly wrong. + } + h.dec_ref(); + } + return i; + }); + + m.def("pass_pyobject_ptr_and_int", [](PyObject *, int) {}); + +#ifdef PYBIND11_NO_COMPILE_SECTION // Change to ifndef for manual testing. + { + PyObject *ptr = nullptr; + (void) py::cast(*ptr); + } +#endif +} diff --git a/pybind11/tests/test_type_caster_pyobject_ptr.py b/pybind11/tests/test_type_caster_pyobject_ptr.py new file mode 100644 index 000000000..1f1ece2ba --- /dev/null +++ b/pybind11/tests/test_type_caster_pyobject_ptr.py @@ -0,0 +1,104 @@ +import pytest + +from pybind11_tests import type_caster_pyobject_ptr as m + + +# For use as a temporary user-defined object, to maximize sensitivity of the tests below. +class ValueHolder: + def __init__(self, value): + self.value = value + + +def test_cast_from_pyobject_ptr(): + assert m.cast_from_pyobject_ptr() == 6758 + + +def test_cast_handle_to_pyobject_ptr(): + assert m.cast_handle_to_pyobject_ptr(ValueHolder(24)) == 76 + + +def test_cast_object_to_pyobject_ptr(): + assert m.cast_object_to_pyobject_ptr(ValueHolder(43)) == 257 + + +def test_cast_list_to_pyobject_ptr(): + assert m.cast_list_to_pyobject_ptr([1, 2, 3, 4, 5]) == 395 + + +def test_return_pyobject_ptr(): + assert m.return_pyobject_ptr() == 2314 + + +def test_pass_pyobject_ptr(): + assert m.pass_pyobject_ptr(ValueHolder(82)) == 118 + + +@pytest.mark.parametrize( + "call_callback", + [ + m.call_callback_with_object_return, + m.call_callback_with_pyobject_ptr_return, + ], +) +def test_call_callback_with_object_return(call_callback): + def cb(value): + if value < 0: + raise ValueError("Raised from cb") + return ValueHolder(1000 - value) + + assert call_callback(cb, 287).value == 713 + + with pytest.raises(ValueError, match="^Raised from cb$"): + call_callback(cb, -1) + + +def test_call_callback_with_pyobject_ptr_arg(): + def cb(obj): + return 300 - obj.value + + assert m.call_callback_with_pyobject_ptr_arg(cb, ValueHolder(39)) == 261 + + +@pytest.mark.parametrize("set_error", [True, False]) +def test_cast_to_python_nullptr(set_error): + expected = { + True: r"^Reflective of healthy error handling\.$", + False: ( + r"^Internal error: pybind11::error_already_set called " + r"while Python error indicator not set\.$" + ), + }[set_error] + with pytest.raises(RuntimeError, match=expected): + m.cast_to_pyobject_ptr_nullptr(set_error) + + +def test_cast_to_python_non_nullptr_with_error_set(): + with pytest.raises(SystemError) as excinfo: + m.cast_to_pyobject_ptr_non_nullptr_with_error_set() + assert str(excinfo.value) == "src != nullptr but PyErr_Occurred()" + assert str(excinfo.value.__cause__) == "Reflective of unhealthy error handling." + + +def test_pass_list_pyobject_ptr(): + acc = m.pass_list_pyobject_ptr([ValueHolder(842), ValueHolder(452)]) + assert acc == 842452 + + +def test_return_list_pyobject_ptr_take_ownership(): + vec_obj = m.return_list_pyobject_ptr_take_ownership(ValueHolder) + assert [e.value for e in vec_obj] == [93, 186] + + +def test_return_list_pyobject_ptr_reference(): + vec_obj = m.return_list_pyobject_ptr_reference(ValueHolder) + assert [e.value for e in vec_obj] == [93, 186] + # Commenting out the next `assert` will leak the Python references. + # An easy way to see evidence of the leaks: + # Insert `while True:` as the first line of this function and monitor the + # process RES (Resident Memory Size) with the Unix top command. + assert m.dec_ref_each_pyobject_ptr(vec_obj) == 2 + + +def test_type_caster_name_via_incompatible_function_arguments_type_error(): + with pytest.raises(TypeError, match=r"1\. \(arg0: object, arg1: int\) -> None"): + m.pass_pyobject_ptr_and_int(ValueHolder(101), ValueHolder(202)) diff --git a/pybind11/tests/test_unnamed_namespace_a.cpp b/pybind11/tests/test_unnamed_namespace_a.cpp new file mode 100644 index 000000000..2152e64bd --- /dev/null +++ b/pybind11/tests/test_unnamed_namespace_a.cpp @@ -0,0 +1,38 @@ +#include "pybind11_tests.h" + +namespace { +struct any_struct {}; +} // namespace + +TEST_SUBMODULE(unnamed_namespace_a, m) { + if (py::detail::get_type_info(typeid(any_struct)) == nullptr) { + py::class_(m, "unnamed_namespace_a_any_struct"); + } else { + m.attr("unnamed_namespace_a_any_struct") = py::none(); + } + m.attr("PYBIND11_INTERNALS_VERSION") = PYBIND11_INTERNALS_VERSION; + m.attr("defined_WIN32_or__WIN32") = +#if defined(WIN32) || defined(_WIN32) + true; +#else + false; +#endif + m.attr("defined___clang__") = +#if defined(__clang__) + true; +#else + false; +#endif + m.attr("defined__LIBCPP_VERSION") = +#if defined(_LIBCPP_VERSION) + true; +#else + false; +#endif + m.attr("defined___GLIBCXX__") = +#if defined(__GLIBCXX__) + true; +#else + false; +#endif +} diff --git a/pybind11/tests/test_unnamed_namespace_a.py b/pybind11/tests/test_unnamed_namespace_a.py new file mode 100644 index 000000000..9d9856c5a --- /dev/null +++ b/pybind11/tests/test_unnamed_namespace_a.py @@ -0,0 +1,34 @@ +import pytest + +from pybind11_tests import unnamed_namespace_a as m +from pybind11_tests import unnamed_namespace_b as mb + +XFAIL_CONDITION = ( + "(m.PYBIND11_INTERNALS_VERSION <= 4 and (m.defined___clang__ or not m.defined___GLIBCXX__))" + " or " + "(m.PYBIND11_INTERNALS_VERSION >= 5 and not m.defined_WIN32_or__WIN32" + " and " + "(m.defined___clang__ or m.defined__LIBCPP_VERSION))" +) +XFAIL_REASON = "Known issues: https://github.com/pybind/pybind11/pull/4319" + + +@pytest.mark.xfail(XFAIL_CONDITION, reason=XFAIL_REASON, strict=False) +@pytest.mark.parametrize( + "any_struct", [m.unnamed_namespace_a_any_struct, mb.unnamed_namespace_b_any_struct] +) +def test_have_class_any_struct(any_struct): + assert any_struct is not None + + +def test_have_at_least_one_class_any_struct(): + assert ( + m.unnamed_namespace_a_any_struct is not None + or mb.unnamed_namespace_b_any_struct is not None + ) + + +@pytest.mark.xfail(XFAIL_CONDITION, reason=XFAIL_REASON, strict=True) +def test_have_both_class_any_struct(): + assert m.unnamed_namespace_a_any_struct is not None + assert mb.unnamed_namespace_b_any_struct is not None diff --git a/pybind11/tests/test_unnamed_namespace_b.cpp b/pybind11/tests/test_unnamed_namespace_b.cpp new file mode 100644 index 000000000..f97757a7d --- /dev/null +++ b/pybind11/tests/test_unnamed_namespace_b.cpp @@ -0,0 +1,13 @@ +#include "pybind11_tests.h" + +namespace { +struct any_struct {}; +} // namespace + +TEST_SUBMODULE(unnamed_namespace_b, m) { + if (py::detail::get_type_info(typeid(any_struct)) == nullptr) { + py::class_(m, "unnamed_namespace_b_any_struct"); + } else { + m.attr("unnamed_namespace_b_any_struct") = py::none(); + } +} diff --git a/pybind11/tests/test_unnamed_namespace_b.py b/pybind11/tests/test_unnamed_namespace_b.py new file mode 100644 index 000000000..4bcaa7a6c --- /dev/null +++ b/pybind11/tests/test_unnamed_namespace_b.py @@ -0,0 +1,5 @@ +from pybind11_tests import unnamed_namespace_b as m + + +def test_have_attr_any_struct(): + assert hasattr(m, "unnamed_namespace_b_any_struct") diff --git a/pybind11/tests/test_vector_unique_ptr_member.cpp b/pybind11/tests/test_vector_unique_ptr_member.cpp new file mode 100644 index 000000000..680cf9a6d --- /dev/null +++ b/pybind11/tests/test_vector_unique_ptr_member.cpp @@ -0,0 +1,54 @@ +#include "pybind11_tests.h" + +#include +#include +#include + +namespace pybind11_tests { +namespace vector_unique_ptr_member { + +struct DataType {}; + +// Reduced from a use case in the wild. +struct VectorOwner { + static std::unique_ptr Create(std::size_t num_elems) { + return std::unique_ptr( + new VectorOwner(std::vector>(num_elems))); + } + + std::size_t data_size() const { return data_.size(); } + +private: + explicit VectorOwner(std::vector> data) : data_(std::move(data)) {} + + const std::vector> data_; +}; + +} // namespace vector_unique_ptr_member +} // namespace pybind11_tests + +namespace pybind11 { +namespace detail { + +template <> +struct is_copy_constructible + : std::false_type {}; + +template <> +struct is_move_constructible + : std::false_type {}; + +} // namespace detail +} // namespace pybind11 + +using namespace pybind11_tests::vector_unique_ptr_member; + +py::object py_cast_VectorOwner_ptr(VectorOwner *ptr) { return py::cast(ptr); } + +TEST_SUBMODULE(vector_unique_ptr_member, m) { + py::class_(m, "VectorOwner") + .def_static("Create", &VectorOwner::Create) + .def("data_size", &VectorOwner::data_size); + + m.def("py_cast_VectorOwner_ptr", py_cast_VectorOwner_ptr); +} diff --git a/pybind11/tests/test_vector_unique_ptr_member.py b/pybind11/tests/test_vector_unique_ptr_member.py new file mode 100644 index 000000000..2da3d97c3 --- /dev/null +++ b/pybind11/tests/test_vector_unique_ptr_member.py @@ -0,0 +1,14 @@ +import pytest + +from pybind11_tests import vector_unique_ptr_member as m + + +@pytest.mark.parametrize("num_elems", range(3)) +def test_create(num_elems): + vo = m.VectorOwner.Create(num_elems) + assert vo.data_size() == num_elems + + +def test_cast(): + vo = m.VectorOwner.Create(0) + assert m.py_cast_VectorOwner_ptr(vo) is vo diff --git a/pybind11/tests/test_virtual_functions.cpp b/pybind11/tests/test_virtual_functions.cpp index 323aa0d22..93b136ad3 100644 --- a/pybind11/tests/test_virtual_functions.cpp +++ b/pybind11/tests/test_virtual_functions.cpp @@ -173,7 +173,8 @@ struct AdderBase { using DataVisitor = std::function; virtual void - operator()(const Data &first, const Data &second, const DataVisitor &visitor) const = 0; + operator()(const Data &first, const Data &second, const DataVisitor &visitor) const + = 0; virtual ~AdderBase() = default; AdderBase() = default; AdderBase(const AdderBase &) = delete; diff --git a/pybind11/tests/test_virtual_functions.py b/pybind11/tests/test_virtual_functions.py index 4d00d3690..c17af7df5 100644 --- a/pybind11/tests/test_virtual_functions.py +++ b/pybind11/tests/test_virtual_functions.py @@ -192,8 +192,7 @@ def test_move_support(): class NCVirtExt(m.NCVirt): def get_noncopyable(self, a, b): # Constructs and returns a new instance: - nc = m.NonCopyable(a * a, b * b) - return nc + return m.NonCopyable(a * a, b * b) def get_movable(self, a, b): # Return a referenced copy @@ -256,7 +255,7 @@ def test_dispatch_issue(msg): assert m.dispatch_issue_go(b) == "Yay.." -def test_recursive_dispatch_issue(msg): +def test_recursive_dispatch_issue(): """#3357: Recursive dispatch fails to find python function override""" class Data(m.Data): @@ -269,7 +268,7 @@ def test_recursive_dispatch_issue(msg): # lambda is a workaround, which adds extra frame to the # current CPython thread. Removing lambda reveals the bug # [https://github.com/pybind/pybind11/issues/3357] - (lambda: visitor(Data(first.value + second.value)))() + (lambda: visitor(Data(first.value + second.value)))() # noqa: PLC3002 class StoreResultVisitor: def __init__(self): diff --git a/pybind11/tools/FindCatch.cmake b/pybind11/tools/FindCatch.cmake index 57bba58b3..5d3fcbfb1 100644 --- a/pybind11/tools/FindCatch.cmake +++ b/pybind11/tools/FindCatch.cmake @@ -36,10 +36,14 @@ endfunction() function(_download_catch version destination_dir) message(STATUS "Downloading catch v${version}...") set(url https://github.com/philsquared/Catch/releases/download/v${version}/catch.hpp) - file(DOWNLOAD ${url} "${destination_dir}/catch.hpp" STATUS status) + file( + DOWNLOAD ${url} "${destination_dir}/catch.hpp" + STATUS status + LOG log) list(GET status 0 error) if(error) - message(FATAL_ERROR "Could not download ${url}") + string(REPLACE "\n" "\n " log " ${log}") + message(FATAL_ERROR "Could not download URL:\n" " ${url}\n" "Log:\n" "${log}") endif() set(CATCH_INCLUDE_DIR "${destination_dir}" diff --git a/pybind11/tools/FindPythonLibsNew.cmake b/pybind11/tools/FindPythonLibsNew.cmake index a5a628fd7..ce558d4ec 100644 --- a/pybind11/tools/FindPythonLibsNew.cmake +++ b/pybind11/tools/FindPythonLibsNew.cmake @@ -151,9 +151,13 @@ if(NOT _PYTHON_SUCCESS MATCHES 0) return() endif() +option( + PYBIND11_PYTHONLIBS_OVERWRITE + "Overwrite cached values read from Python library (classic search). Turn off if cross-compiling and manually setting these values." + ON) # Can manually set values when cross-compiling macro(_PYBIND11_GET_IF_UNDEF lst index name) - if(NOT DEFINED "${name}") + if(PYBIND11_PYTHONLIBS_OVERWRITE OR NOT DEFINED "${name}") list(GET "${lst}" "${index}" "${name}") endif() endmacro() @@ -204,7 +208,9 @@ string(REGEX REPLACE "\\\\" "/" PYTHON_PREFIX "${PYTHON_PREFIX}") string(REGEX REPLACE "\\\\" "/" PYTHON_INCLUDE_DIR "${PYTHON_INCLUDE_DIR}") string(REGEX REPLACE "\\\\" "/" PYTHON_SITE_PACKAGES "${PYTHON_SITE_PACKAGES}") -if(CMAKE_HOST_WIN32) +if(DEFINED PYTHON_LIBRARY) + # Don't write to PYTHON_LIBRARY if it's already set +elseif(CMAKE_HOST_WIN32) set(PYTHON_LIBRARY "${PYTHON_PREFIX}/libs/python${PYTHON_LIBRARY_SUFFIX}.lib") # when run in a venv, PYTHON_PREFIX points to it. But the libraries remain in the @@ -270,7 +276,7 @@ if(NOT PYTHON_DEBUG_LIBRARY) endif() set(PYTHON_DEBUG_LIBRARIES "${PYTHON_DEBUG_LIBRARY}") -find_package_message(PYTHON "Found PythonLibs: ${PYTHON_LIBRARY}" +find_package_message(PYTHON "Found PythonLibs: ${PYTHON_LIBRARIES}" "${PYTHON_EXECUTABLE}${PYTHON_VERSION_STRING}") set(PYTHONLIBS_FOUND TRUE) diff --git a/pybind11/tools/JoinPaths.cmake b/pybind11/tools/JoinPaths.cmake new file mode 100644 index 000000000..c68d91b84 --- /dev/null +++ b/pybind11/tools/JoinPaths.cmake @@ -0,0 +1,23 @@ +# This module provides function for joining paths +# known from most languages +# +# SPDX-License-Identifier: (MIT OR CC0-1.0) +# Copyright 2020 Jan Tojnar +# https://github.com/jtojnar/cmake-snips +# +# Modelled after Python’s os.path.join +# https://docs.python.org/3.7/library/os.path.html#os.path.join +# Windows not supported +function(join_paths joined_path first_path_segment) + set(temp_path "${first_path_segment}") + foreach(current_segment IN LISTS ARGN) + if(NOT ("${current_segment}" STREQUAL "")) + if(IS_ABSOLUTE "${current_segment}") + set(temp_path "${current_segment}") + else() + set(temp_path "${temp_path}/${current_segment}") + endif() + endif() + endforeach() + set(${joined_path} "${temp_path}" PARENT_SCOPE) +endfunction() diff --git a/pybind11/tools/codespell_ignore_lines_from_errors.py b/pybind11/tools/codespell_ignore_lines_from_errors.py new file mode 100644 index 000000000..4ec9add12 --- /dev/null +++ b/pybind11/tools/codespell_ignore_lines_from_errors.py @@ -0,0 +1,39 @@ +"""Simple script for rebuilding .codespell-ignore-lines + +Usage: + +cat < /dev/null > .codespell-ignore-lines +pre-commit run --all-files codespell >& /tmp/codespell_errors.txt +python3 tools/codespell_ignore_lines_from_errors.py /tmp/codespell_errors.txt > .codespell-ignore-lines + +git diff to review changes, then commit, push. +""" + +import sys +from typing import List + + +def run(args: List[str]) -> None: + assert len(args) == 1, "codespell_errors.txt" + cache = {} + done = set() + with open(args[0]) as f: + lines = f.read().splitlines() + + for line in sorted(lines): + i = line.find(" ==> ") + if i > 0: + flds = line[:i].split(":") + if len(flds) >= 2: + filename, line_num = flds[:2] + if filename not in cache: + with open(filename) as f: + cache[filename] = f.read().splitlines() + supp = cache[filename][int(line_num) - 1] + if supp not in done: + print(supp) + done.add(supp) + + +if __name__ == "__main__": + run(args=sys.argv[1:]) diff --git a/pybind11/tools/make_changelog.py b/pybind11/tools/make_changelog.py index 839040a93..b5bd83294 100755 --- a/pybind11/tools/make_changelog.py +++ b/pybind11/tools/make_changelog.py @@ -31,8 +31,10 @@ issues = (issue for page in issues_pages for issue in page) missing = [] for issue in issues: - changelog = ENTRY.findall(issue.body) - if changelog: + changelog = ENTRY.findall(issue.body or "") + if not changelog or not changelog[0]: + missing.append(issue) + else: (msg,) = changelog if not msg.startswith("* "): msg = "* " + msg @@ -44,9 +46,6 @@ for issue in issues: print(Syntax(msg, "rst", theme="ansi_light", word_wrap=True)) print() - else: - missing.append(issue) - if missing: print() print("[blue]" + "-" * 30) diff --git a/pybind11/tools/pybind11.pc.in b/pybind11/tools/pybind11.pc.in new file mode 100644 index 000000000..402f0b357 --- /dev/null +++ b/pybind11/tools/pybind11.pc.in @@ -0,0 +1,7 @@ +prefix=@prefix_for_pc_file@ +includedir=@includedir_for_pc_file@ + +Name: @PROJECT_NAME@ +Description: Seamless operability between C++11 and Python +Version: @PROJECT_VERSION@ +Cflags: -I${includedir} diff --git a/pybind11/tools/pybind11Common.cmake b/pybind11/tools/pybind11Common.cmake index e1fb601ac..308d1b70d 100644 --- a/pybind11/tools/pybind11Common.cmake +++ b/pybind11/tools/pybind11Common.cmake @@ -5,8 +5,8 @@ Adds the following targets:: pybind11::pybind11 - link to headers and pybind11 pybind11::module - Adds module links pybind11::embed - Adds embed links - pybind11::lto - Link time optimizations (manual selection) - pybind11::thin_lto - Link time optimizations (manual selection) + pybind11::lto - Link time optimizations (only if CMAKE_INTERPROCEDURAL_OPTIMIZATION is not set) + pybind11::thin_lto - Link time optimizations (only if CMAKE_INTERPROCEDURAL_OPTIMIZATION is not set) pybind11::python_link_helper - Adds link to Python libraries pybind11::windows_extras - MSVC bigobj and mp for building multithreaded pybind11::opt_size - avoid optimizations that increase code size @@ -20,7 +20,7 @@ Adds the following functions:: # CMake 3.10 has an include_guard command, but we can't use that yet # include_guard(global) (pre-CMake 3.10) -if(TARGET pybind11::lto) +if(TARGET pybind11::pybind11) return() endif() @@ -163,11 +163,19 @@ endif() # --------------------- Python specifics ------------------------- +# CMake 3.27 removes the classic FindPythonInterp if CMP0148 is NEW +if(CMAKE_VERSION VERSION_LESS "3.27") + set(_pybind11_missing_old_python "OLD") +else() + cmake_policy(GET CMP0148 _pybind11_missing_old_python) +endif() + # Check to see which Python mode we are in, new, old, or no python if(PYBIND11_NOPYTHON) set(_pybind11_nopython ON) elseif( - PYBIND11_FINDPYTHON + _pybind11_missing_old_python STREQUAL "NEW" + OR PYBIND11_FINDPYTHON OR Python_FOUND OR Python2_FOUND OR Python3_FOUND) @@ -311,6 +319,16 @@ function(_pybind11_generate_lto target prefer_thin_lto) HAS_FLTO "-flto${cxx_append}" "-flto${linker_append}" PYBIND11_LTO_CXX_FLAGS PYBIND11_LTO_LINKER_FLAGS) endif() + elseif(CMAKE_CXX_COMPILER_ID MATCHES "IntelLLVM") + # IntelLLVM equivalent to LTO is called IPO; also IntelLLVM is WIN32/UNIX + # WARNING/HELP WANTED: This block of code is currently not covered by pybind11 GitHub Actions! + if(WIN32) + _pybind11_return_if_cxx_and_linker_flags_work( + HAS_INTEL_IPO "-Qipo" "-Qipo" PYBIND11_LTO_CXX_FLAGS PYBIND11_LTO_LINKER_FLAGS) + else() + _pybind11_return_if_cxx_and_linker_flags_work( + HAS_INTEL_IPO "-ipo" "-ipo" PYBIND11_LTO_CXX_FLAGS PYBIND11_LTO_LINKER_FLAGS) + endif() elseif(CMAKE_CXX_COMPILER_ID MATCHES "Intel") # Intel equivalent to LTO is called IPO _pybind11_return_if_cxx_and_linker_flags_work(HAS_INTEL_IPO "-ipo" "-ipo" @@ -362,11 +380,13 @@ function(_pybind11_generate_lto target prefer_thin_lto) endif() endfunction() -add_library(pybind11::lto IMPORTED INTERFACE ${optional_global}) -_pybind11_generate_lto(pybind11::lto FALSE) +if(NOT DEFINED CMAKE_INTERPROCEDURAL_OPTIMIZATION) + add_library(pybind11::lto IMPORTED INTERFACE ${optional_global}) + _pybind11_generate_lto(pybind11::lto FALSE) -add_library(pybind11::thin_lto IMPORTED INTERFACE ${optional_global}) -_pybind11_generate_lto(pybind11::thin_lto TRUE) + add_library(pybind11::thin_lto IMPORTED INTERFACE ${optional_global}) + _pybind11_generate_lto(pybind11::thin_lto TRUE) +endif() # ---------------------- pybind11_strip ----------------------------- diff --git a/pybind11/tools/pybind11Config.cmake.in b/pybind11/tools/pybind11Config.cmake.in index 9383e8c67..5734f437b 100644 --- a/pybind11/tools/pybind11Config.cmake.in +++ b/pybind11/tools/pybind11Config.cmake.in @@ -63,7 +63,9 @@ Modes There are two modes provided; classic, which is built on the old Python discovery packages in CMake, or the new FindPython mode, which uses FindPython -from 3.12+ forward (3.15+ _highly_ recommended). +from 3.12+ forward (3.15+ _highly_ recommended). If you set the minimum or +maximum version of CMake to 3.27+, then FindPython is the default (since +FindPythonInterp/FindPythonLibs has been removed via policy `CMP0148`). New FindPython mode ^^^^^^^^^^^^^^^^^^^ diff --git a/pybind11/tools/pybind11NewTools.cmake b/pybind11/tools/pybind11NewTools.cmake index abba0fe0e..7d7424a79 100644 --- a/pybind11/tools/pybind11NewTools.cmake +++ b/pybind11/tools/pybind11NewTools.cmake @@ -9,7 +9,7 @@ if(CMAKE_VERSION VERSION_LESS 3.12) message(FATAL_ERROR "You cannot use the new FindPython module with CMake < 3.12") endif() -include_guard(GLOBAL) +include_guard(DIRECTORY) get_property( is_config @@ -233,7 +233,9 @@ function(pybind11_add_module target_name) endif() endif() - if(NOT MSVC AND NOT ${CMAKE_BUILD_TYPE} MATCHES Debug|RelWithDebInfo) + # Use case-insensitive comparison to match the result of $ + string(TOUPPER "${CMAKE_BUILD_TYPE}" uppercase_CMAKE_BUILD_TYPE) + if(NOT MSVC AND NOT "${uppercase_CMAKE_BUILD_TYPE}" MATCHES DEBUG|RELWITHDEBINFO) # Strip unnecessary sections of the binary on Linux/macOS pybind11_strip(${target_name}) endif() diff --git a/pybind11/tools/pybind11Tools.cmake b/pybind11/tools/pybind11Tools.cmake index 5535e872f..66ad00a47 100644 --- a/pybind11/tools/pybind11Tools.cmake +++ b/pybind11/tools/pybind11Tools.cmake @@ -115,6 +115,7 @@ if(PYTHON_IS_DEBUG) PROPERTY INTERFACE_COMPILE_DEFINITIONS Py_DEBUG) endif() +# The <3.11 code here does not support release/debug builds at the same time, like on vcpkg if(CMAKE_VERSION VERSION_LESS 3.11) set_property( TARGET pybind11::module @@ -130,16 +131,19 @@ if(CMAKE_VERSION VERSION_LESS 3.11) APPEND PROPERTY INTERFACE_LINK_LIBRARIES pybind11::pybind11 $) else() + # The IMPORTED INTERFACE library here is to ensure that "debug" and "release" get processed outside + # of a generator expression - https://gitlab.kitware.com/cmake/cmake/-/issues/18424, as they are + # target_link_library keywords rather than real libraries. + add_library(pybind11::_ClassicPythonLibraries IMPORTED INTERFACE) + target_link_libraries(pybind11::_ClassicPythonLibraries INTERFACE ${PYTHON_LIBRARIES}) target_link_libraries( pybind11::module INTERFACE pybind11::python_link_helper - "$<$,$>:$>" - ) + "$<$,$>:pybind11::_ClassicPythonLibraries>") target_link_libraries(pybind11::embed INTERFACE pybind11::pybind11 - $) - + pybind11::_ClassicPythonLibraries) endif() function(pybind11_extension name) @@ -208,7 +212,9 @@ function(pybind11_add_module target_name) endif() endif() - if(NOT MSVC AND NOT ${CMAKE_BUILD_TYPE} MATCHES Debug|RelWithDebInfo) + # Use case-insensitive comparison to match the result of $ + string(TOUPPER "${CMAKE_BUILD_TYPE}" uppercase_CMAKE_BUILD_TYPE) + if(NOT MSVC AND NOT "${uppercase_CMAKE_BUILD_TYPE}" MATCHES DEBUG|RELWITHDEBINFO) pybind11_strip(${target_name}) endif() diff --git a/pybind11/tools/setup_global.py.in b/pybind11/tools/setup_global.py.in index 8aa387178..885ac5c72 100644 --- a/pybind11/tools/setup_global.py.in +++ b/pybind11/tools/setup_global.py.in @@ -27,9 +27,11 @@ class InstallHeadersNested(install_headers): main_headers = glob.glob("pybind11/include/pybind11/*.h") detail_headers = glob.glob("pybind11/include/pybind11/detail/*.h") +eigen_headers = glob.glob("pybind11/include/pybind11/eigen/*.h") stl_headers = glob.glob("pybind11/include/pybind11/stl/*.h") cmake_files = glob.glob("pybind11/share/cmake/pybind11/*.cmake") -headers = main_headers + detail_headers + stl_headers +pkgconfig_files = glob.glob("pybind11/share/pkgconfig/*.pc") +headers = main_headers + detail_headers + stl_headers + eigen_headers cmdclass = {"install_headers": InstallHeadersNested} $extra_cmd @@ -51,8 +53,10 @@ setup( headers=headers, data_files=[ (base + "share/cmake/pybind11", cmake_files), + (base + "share/pkgconfig", pkgconfig_files), (base + "include/pybind11", main_headers), (base + "include/pybind11/detail", detail_headers), + (base + "include/pybind11/eigen", eigen_headers), (base + "include/pybind11/stl", stl_headers), ], cmdclass=cmdclass, diff --git a/pybind11/tools/setup_main.py.in b/pybind11/tools/setup_main.py.in index 738d73faa..6358cc7b9 100644 --- a/pybind11/tools/setup_main.py.in +++ b/pybind11/tools/setup_main.py.in @@ -15,15 +15,19 @@ setup( "pybind11", "pybind11.include.pybind11", "pybind11.include.pybind11.detail", + "pybind11.include.pybind11.eigen", "pybind11.include.pybind11.stl", "pybind11.share.cmake.pybind11", + "pybind11.share.pkgconfig", ], package_data={ "pybind11": ["py.typed"], "pybind11.include.pybind11": ["*.h"], "pybind11.include.pybind11.detail": ["*.h"], + "pybind11.include.pybind11.eigen": ["*.h"], "pybind11.include.pybind11.stl": ["*.h"], "pybind11.share.cmake.pybind11": ["*.cmake"], + "pybind11.share.pkgconfig": ["*.pc"], }, extras_require={ "global": ["pybind11_global==$version"] From fe2080f2b13404f05f5b3c75de0725e39a79e7c2 Mon Sep 17 00:00:00 2001 From: Varun Agrawal Date: Tue, 5 Sep 2023 12:04:54 -0400 Subject: [PATCH 057/113] fix warnings due to PEP 420 --- python/setup.py.in | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/python/setup.py.in b/python/setup.py.in index 9aa4b71f4..e15e39075 100644 --- a/python/setup.py.in +++ b/python/setup.py.in @@ -1,19 +1,17 @@ """Setup file to install the GTSAM package.""" -try: - from setuptools import setup, find_packages -except ImportError: - from distutils.core import setup, find_packages +from setuptools import setup, find_namespace_packages -packages = find_packages(where=".") +packages = find_namespace_packages( + where=".", + exclude=('build', 'build.*', 'CMakeFiles', 'CMakeFiles.*', + 'gtsam.notebooks', '*.preamble', '*.specializations', 'dist')) print("PACKAGES: ", packages) package_data = { '': [ "./*.so", - "./*.dll", - "Data/*" # Add the data files to the package - "Data/**/*" # Add the data files in subdirectories + "./*.dll" ] } @@ -41,7 +39,6 @@ setup( 'Operating System :: Microsoft :: Windows', 'Operating System :: POSIX', 'License :: OSI Approved :: BSD License', - 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 3', ], packages=packages, From 67ef015e0c536cee576450e4cad0b87e2cbc2cfb Mon Sep 17 00:00:00 2001 From: Varun Agrawal Date: Tue, 5 Sep 2023 12:36:56 -0400 Subject: [PATCH 058/113] separate required and dev dependencies --- python/dev_requirements.txt | 2 ++ python/requirements.txt | 1 - 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 python/dev_requirements.txt diff --git a/python/dev_requirements.txt b/python/dev_requirements.txt new file mode 100644 index 000000000..6970ee613 --- /dev/null +++ b/python/dev_requirements.txt @@ -0,0 +1,2 @@ +-r requirements.txt +pyparsing>=2.4.2 \ No newline at end of file diff --git a/python/requirements.txt b/python/requirements.txt index 481d27d8e..099cc80d6 100644 --- a/python/requirements.txt +++ b/python/requirements.txt @@ -1,2 +1 @@ numpy>=1.11.0 -pyparsing>=2.4.2 From 42e4a4f6daab701bdcf0b404ef9f1c40fbb101cf Mon Sep 17 00:00:00 2001 From: Varun Agrawal Date: Tue, 5 Sep 2023 12:37:12 -0400 Subject: [PATCH 059/113] install dev dependencies using CMake --- python/CMakeLists.txt | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/python/CMakeLists.txt b/python/CMakeLists.txt index 2b2abf507..f67cbb67e 100644 --- a/python/CMakeLists.txt +++ b/python/CMakeLists.txt @@ -1,9 +1,16 @@ set(GTSAM_PYTHON_BUILD_DIRECTORY ${PROJECT_BINARY_DIR}/python) +set(PROJECT_PYTHON_SOURCE_DIR ${PROJECT_SOURCE_DIR}/python) if (NOT GTSAM_BUILD_PYTHON) return() endif() +# Install development dependencies to build wrapper +message(STATUS "Installing Python development dependencies") +execute_process(COMMAND "${PYTHON_EXECUTABLE}" "-m" "pip" "install" "-r" "dev_requirements.txt" + WORKING_DIRECTORY ${PROJECT_PYTHON_SOURCE_DIR} + OUTPUT_QUIET) + # Generate setup.py. file(READ "${PROJECT_SOURCE_DIR}/README.md" README_CONTENTS) configure_file(${PROJECT_SOURCE_DIR}/python/setup.py.in From cb661a9f8929dfb064b2eca75091542e7562ac5e Mon Sep 17 00:00:00 2001 From: Varun Agrawal Date: Tue, 5 Sep 2023 12:38:37 -0400 Subject: [PATCH 060/113] use variable for Python source directory --- python/CMakeLists.txt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/python/CMakeLists.txt b/python/CMakeLists.txt index f67cbb67e..61c9797c2 100644 --- a/python/CMakeLists.txt +++ b/python/CMakeLists.txt @@ -1,5 +1,5 @@ -set(GTSAM_PYTHON_BUILD_DIRECTORY ${PROJECT_BINARY_DIR}/python) set(PROJECT_PYTHON_SOURCE_DIR ${PROJECT_SOURCE_DIR}/python) +set(GTSAM_PYTHON_BUILD_DIRECTORY ${PROJECT_BINARY_DIR}/python) if (NOT GTSAM_BUILD_PYTHON) return() @@ -13,11 +13,11 @@ execute_process(COMMAND "${PYTHON_EXECUTABLE}" "-m" "pip" "install" "-r" "dev_re # Generate setup.py. file(READ "${PROJECT_SOURCE_DIR}/README.md" README_CONTENTS) -configure_file(${PROJECT_SOURCE_DIR}/python/setup.py.in +configure_file(${PROJECT_PYTHON_SOURCE_DIR}/setup.py.in ${GTSAM_PYTHON_BUILD_DIRECTORY}/setup.py) # Supply MANIFEST.in for older versions of Python -file(COPY ${PROJECT_SOURCE_DIR}/python/MANIFEST.in +file(COPY ${PROJECT_PYTHON_SOURCE_DIR}/MANIFEST.in DESTINATION ${GTSAM_PYTHON_BUILD_DIRECTORY}) set(WRAP_BUILD_TYPE_POSTFIXES ${GTSAM_BUILD_TYPE_POSTFIXES}) @@ -106,7 +106,7 @@ pybind_wrap(${GTSAM_PYTHON_TARGET} # target "gtsam" # module_name "gtsam" # top_namespace "${ignore}" # ignore_classes - ${PROJECT_SOURCE_DIR}/python/gtsam/gtsam.tpl + ${PROJECT_PYTHON_SOURCE_DIR}/gtsam/gtsam.tpl gtsam # libs "gtsam;gtsam_header" # dependencies ${GTSAM_ENABLE_BOOST_SERIALIZATION} # use_boost_serialization @@ -185,7 +185,7 @@ if(GTSAM_UNSTABLE_BUILD_PYTHON) "gtsam_unstable" # module_name "gtsam" # top_namespace "${ignore}" # ignore_classes - ${PROJECT_SOURCE_DIR}/python/gtsam_unstable/gtsam_unstable.tpl + ${PROJECT_PYTHON_SOURCE_DIR}/gtsam_unstable/gtsam_unstable.tpl gtsam_unstable # libs "gtsam_unstable;gtsam_unstable_header" # dependencies ${GTSAM_ENABLE_BOOST_SERIALIZATION} # use_boost_serialization From b8ec7650350ac8c423e22903affe8bda7f4ae28c Mon Sep 17 00:00:00 2001 From: Varun Agrawal Date: Tue, 5 Sep 2023 12:40:10 -0400 Subject: [PATCH 061/113] remove requirements install step as it is now a part of the cmake process --- .github/scripts/python.sh | 2 -- docker/ubuntu-gtsam-python/Dockerfile | 3 --- python/README.md | 2 +- 3 files changed, 1 insertion(+), 6 deletions(-) diff --git a/.github/scripts/python.sh b/.github/scripts/python.sh index c72e9abd6..08b8084a0 100644 --- a/.github/scripts/python.sh +++ b/.github/scripts/python.sh @@ -39,8 +39,6 @@ function install_dependencies() if [ "${GTSAM_WITH_TBB:-OFF}" == "ON" ]; then install_tbb fi - - $PYTHON -m pip install -r $GITHUB_WORKSPACE/python/requirements.txt } function build() diff --git a/docker/ubuntu-gtsam-python/Dockerfile b/docker/ubuntu-gtsam-python/Dockerfile index 85eed4d4e..4a7c4b37f 100644 --- a/docker/ubuntu-gtsam-python/Dockerfile +++ b/docker/ubuntu-gtsam-python/Dockerfile @@ -6,9 +6,6 @@ FROM borglab/ubuntu-gtsam:bionic # Install pip RUN apt-get install -y python3-pip python3-dev -# Install python wrapper requirements -RUN python3 -m pip install -U -r /usr/src/gtsam/python/requirements.txt - # Run cmake again, now with python toolbox on WORKDIR /usr/src/gtsam/build RUN cmake \ diff --git a/python/README.md b/python/README.md index 278d62094..e81be74fc 100644 --- a/python/README.md +++ b/python/README.md @@ -16,7 +16,7 @@ For instructions on updating the version of the [wrap library](https://github.co - This wrapper needs `pyparsing(>=2.4.2)`, and `numpy(>=1.11.0)`. These can be installed as follows: ```bash - pip install -r /python/requirements.txt + pip install -r /python/dev_requirements.txt ``` ## Install From 651159f939ad85090902fd89264cc7a0b01b937d Mon Sep 17 00:00:00 2001 From: Varun Agrawal Date: Thu, 7 Sep 2023 16:29:37 -0400 Subject: [PATCH 062/113] remove std:: from return type pair --- gtsam/discrete/discrete.i | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gtsam/discrete/discrete.i b/gtsam/discrete/discrete.i index fe8cbc7f3..a1731f8e5 100644 --- a/gtsam/discrete/discrete.i +++ b/gtsam/discrete/discrete.i @@ -280,11 +280,11 @@ class DiscreteLookupDAG { }; #include -std::pair +pair EliminateDiscrete(const gtsam::DiscreteFactorGraph& factors, const gtsam::Ordering& frontalKeys); -std::pair +pair EliminateForMPE(const gtsam::DiscreteFactorGraph& factors, const gtsam::Ordering& frontalKeys); From d2e4dff214d5c0fb7b8fd7b93ef2f3d936f280ac Mon Sep 17 00:00:00 2001 From: Jose Luis Blanco-Claraco Date: Tue, 5 Sep 2023 11:10:01 +0200 Subject: [PATCH 063/113] Relax unit test thresholds to fix 32bit issues Use a conditional threshold depending on architecture --- gtsam/geometry/tests/testSphericalCamera.cpp | 25 +++++++++++++------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/gtsam/geometry/tests/testSphericalCamera.cpp b/gtsam/geometry/tests/testSphericalCamera.cpp index 4bc851f35..cf8970dc4 100644 --- a/gtsam/geometry/tests/testSphericalCamera.cpp +++ b/gtsam/geometry/tests/testSphericalCamera.cpp @@ -24,6 +24,13 @@ #include #include +#if defined(__i686__) || defined(__i386__) +// See issue discussion: https://github.com/borglab/gtsam/issues/1605 +constexpr double TEST_THRESHOLD = 1e-5; +#else +constexpr double TEST_THRESHOLD = 1e-7; +#endif + using namespace std::placeholders; using namespace std; using namespace gtsam; @@ -104,8 +111,8 @@ TEST(SphericalCamera, Dproject) { Matrix numerical_pose = numericalDerivative21(project3, pose, point1); Matrix numerical_point = numericalDerivative22(project3, pose, point1); EXPECT(assert_equal(bearing1, result)); - EXPECT(assert_equal(numerical_pose, Dpose, 1e-7)); - EXPECT(assert_equal(numerical_point, Dpoint, 1e-7)); + EXPECT(assert_equal(numerical_pose, Dpose, TEST_THRESHOLD)); + EXPECT(assert_equal(numerical_point, Dpoint, TEST_THRESHOLD)); } /* ************************************************************************* */ @@ -123,8 +130,8 @@ TEST(SphericalCamera, reprojectionError) { Matrix numerical_point = numericalDerivative32(reprojectionError2, pose, point1, bearing1); EXPECT(assert_equal(Vector2(0.0, 0.0), result)); - EXPECT(assert_equal(numerical_pose, Dpose, 1e-7)); - EXPECT(assert_equal(numerical_point, Dpoint, 1e-7)); + EXPECT(assert_equal(numerical_pose, Dpose, TEST_THRESHOLD)); + EXPECT(assert_equal(numerical_point, Dpoint, TEST_THRESHOLD)); } /* ************************************************************************* */ @@ -137,9 +144,9 @@ TEST(SphericalCamera, reprojectionError_noisy) { numericalDerivative31(reprojectionError2, pose, point1, bearing_noisy); Matrix numerical_point = numericalDerivative32(reprojectionError2, pose, point1, bearing_noisy); - EXPECT(assert_equal(Vector2(-0.050282, 0.00833482), result, 1e-5)); - EXPECT(assert_equal(numerical_pose, Dpose, 1e-7)); - EXPECT(assert_equal(numerical_point, Dpoint, 1e-7)); + EXPECT(assert_equal(Vector2(-0.050282, 0.00833482), result, 1e2*TEST_THRESHOLD)); + EXPECT(assert_equal(numerical_pose, Dpose, TEST_THRESHOLD)); + EXPECT(assert_equal(numerical_point, Dpoint, TEST_THRESHOLD)); } /* ************************************************************************* */ @@ -151,8 +158,8 @@ TEST(SphericalCamera, Dproject2) { camera.project2(point1, Dpose, Dpoint); Matrix numerical_pose = numericalDerivative21(project3, pose1, point1); Matrix numerical_point = numericalDerivative22(project3, pose1, point1); - CHECK(assert_equal(numerical_pose, Dpose, 1e-7)); - CHECK(assert_equal(numerical_point, Dpoint, 1e-7)); + CHECK(assert_equal(numerical_pose, Dpose, TEST_THRESHOLD)); + CHECK(assert_equal(numerical_point, Dpoint, TEST_THRESHOLD)); } /* ************************************************************************* */ From 87e7a03108dc27dd34a2a0eb561208474b435d34 Mon Sep 17 00:00:00 2001 From: Silvio Traversaro Date: Sat, 9 Sep 2023 16:00:49 +0200 Subject: [PATCH 064/113] Remove setting CMAKE_MACOSX_RPATH to 0 by default --- CMakeLists.txt | 6 ------ 1 file changed, 6 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 309cc7c4e..63c7f9e54 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,11 +1,5 @@ cmake_minimum_required(VERSION 3.0) -# new feature to Cmake Version > 2.8.12 -# Mac ONLY. Define Relative Path on Mac OS -if(NOT DEFINED CMAKE_MACOSX_RPATH) - set(CMAKE_MACOSX_RPATH 0) -endif() - # Set the version number for the library set (GTSAM_VERSION_MAJOR 4) set (GTSAM_VERSION_MINOR 3) From d6e9889f45c6df3e9410db8e3eadfbe61e75c232 Mon Sep 17 00:00:00 2001 From: achintyamohan Date: Sun, 10 Sep 2023 20:07:12 -0400 Subject: [PATCH 065/113] Add cpp example for GNC --- examples/GNCExample.cpp | 67 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 examples/GNCExample.cpp diff --git a/examples/GNCExample.cpp b/examples/GNCExample.cpp new file mode 100644 index 000000000..651e470c8 --- /dev/null +++ b/examples/GNCExample.cpp @@ -0,0 +1,67 @@ +/* ---------------------------------------------------------------------------- + + * GTSAM Copyright 2010, Georgia Tech Research Corporation, + * Atlanta, Georgia 30332-0415 + * All Rights Reserved + * Authors: Frank Dellaert, et al. (see THANKS for the full author list) + + * See LICENSE for the license information + + * -------------------------------------------------------------------------- */ + +/** + * @file GNCExample.cpp + * @brief Simple example showcasing a Graduated Non-Convexity based solver + * @author Achintya Mohan + */ + +#include +#include +#include +#include +#include +#include +#include + +#include + +using namespace std; +using namespace gtsam; + +int main() { + cout << "Graduated Non-Convexity Example\n"; + + NonlinearFactorGraph graph; + + // Add a prior to the first point, set to the origin + auto priorNoise = noiseModel::Isotropic::Sigma(2, 0.1); + graph.addPrior(1, Point2(0.0, 0.0), priorNoise); + + // Add additional factors, noise models must be Gaussian + Point2 x1(1.0, 0.0); + graph.emplace_shared>(1, 2, x1, noiseModel::Isotropic::Sigma(2, 0.2)); + Point2 x2(1.1, 0.1); + graph.emplace_shared>(2, 3, x2, noiseModel::Isotropic::Sigma(2, 0.4)); + + // Initial estimates + Values initial; + initial.insert(1, Point2(0.5, -0.1)); + initial.insert(2, Point2(1.3, 0.1)); + initial.insert(3, Point2(0.8, 0.2)); + + // Set options for the non-minimal solver + LevenbergMarquardtParams lmParams; + lmParams.setMaxIterations(1000); + lmParams.setRelativeErrorTol(1e-5); + + // Set GNC-specific options + GncParams gncParams(lmParams); + gncParams.setLossType(GncLossType::TLS); + + // Optimize the graph and print results + GncOptimizer> optimizer(graph, initial, gncParams); + Values result = optimizer.optimize(); + result.print("Final Result:"); + + return 0; +} \ No newline at end of file From 5cb98a6dcdf7cf4b0624f97e526cff99b4a6b85a Mon Sep 17 00:00:00 2001 From: achintyamohan Date: Mon, 11 Sep 2023 16:54:31 -0400 Subject: [PATCH 066/113] Modify GNC example, add comments to explain example purpose --- examples/GNCExample.cpp | 30 +++++++++++++++++++----------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/examples/GNCExample.cpp b/examples/GNCExample.cpp index 651e470c8..8a4f0706f 100644 --- a/examples/GNCExample.cpp +++ b/examples/GNCExample.cpp @@ -15,7 +15,14 @@ * @author Achintya Mohan */ -#include +/** + * A simple 2D pose graph optimization example + * - The robot is initially at origin (0.0, 0.0, 0.0) + * - We have full odometry measurements for 2 motions + * - The robot first moves to (1.0, 0.0, 0.1) and then to (1.0, 1.0, 0.2) + */ + +#include #include #include #include @@ -34,20 +41,20 @@ int main() { NonlinearFactorGraph graph; // Add a prior to the first point, set to the origin - auto priorNoise = noiseModel::Isotropic::Sigma(2, 0.1); - graph.addPrior(1, Point2(0.0, 0.0), priorNoise); + auto priorNoise = noiseModel::Isotropic::Sigma(3, 0.1); + graph.addPrior(1, Pose2(0.0, 0.0, 0.0), priorNoise); // Add additional factors, noise models must be Gaussian - Point2 x1(1.0, 0.0); - graph.emplace_shared>(1, 2, x1, noiseModel::Isotropic::Sigma(2, 0.2)); - Point2 x2(1.1, 0.1); - graph.emplace_shared>(2, 3, x2, noiseModel::Isotropic::Sigma(2, 0.4)); + Pose2 x1(1.0, 0.0, 0.1); + graph.emplace_shared>(1, 2, x1, noiseModel::Isotropic::Sigma(3, 0.2)); + Pose2 x2(0.0, 1.0, 0.1); + graph.emplace_shared>(2, 3, x2, noiseModel::Isotropic::Sigma(3, 0.4)); // Initial estimates Values initial; - initial.insert(1, Point2(0.5, -0.1)); - initial.insert(2, Point2(1.3, 0.1)); - initial.insert(3, Point2(0.8, 0.2)); + initial.insert(1, Pose2(0.2, 0.5, -0.1)); + initial.insert(2, Pose2(0.8, 0.3, 0.1)); + initial.insert(3, Pose2(0.8, 0.2, 0.3)); // Set options for the non-minimal solver LevenbergMarquardtParams lmParams; @@ -64,4 +71,5 @@ int main() { result.print("Final Result:"); return 0; -} \ No newline at end of file +} + From e859e9932b3c417f45e2c8fedfe72c34ddb069c4 Mon Sep 17 00:00:00 2001 From: Varun Agrawal Date: Fri, 15 Sep 2023 16:27:48 -0400 Subject: [PATCH 067/113] fix CMake variable in doc folder --- doc/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/CMakeLists.txt b/doc/CMakeLists.txt index f0975821f..05e0a13ef 100644 --- a/doc/CMakeLists.txt +++ b/doc/CMakeLists.txt @@ -56,7 +56,7 @@ if (GTSAM_BUILD_DOCS) if (GTSAM_BUILD_UNSTABLE) list(APPEND doc_subdirs ${gtsam_unstable_doc_subdirs}) endif() - if (GTSAM_BUILD_EXAMPLES) + if (GTSAM_BUILD_EXAMPLES_ALWAYS) list(APPEND doc_subdirs examples) endif() From 84b42ed3920a02feb6dd0e424a0aa02df8a50edb Mon Sep 17 00:00:00 2001 From: Varun Agrawal Date: Mon, 18 Sep 2023 22:54:23 -0400 Subject: [PATCH 068/113] use the string argument in DiscreteKeys::print --- gtsam/discrete/DiscreteKey.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/gtsam/discrete/DiscreteKey.cpp b/gtsam/discrete/DiscreteKey.cpp index 79d11d8a7..17f233695 100644 --- a/gtsam/discrete/DiscreteKey.cpp +++ b/gtsam/discrete/DiscreteKey.cpp @@ -49,6 +49,7 @@ namespace gtsam { void DiscreteKeys::print(const std::string& s, const KeyFormatter& keyFormatter) const { + std::cout << (s.empty() ? "" : s + " ") << std::endl; for (auto&& dkey : *this) { std::cout << DefaultKeyFormatter(dkey.first) << " " << dkey.second << std::endl; From bef76b50f9634aec1f9ce0a92ed809ab90ea1807 Mon Sep 17 00:00:00 2001 From: Varun Agrawal Date: Mon, 18 Sep 2023 22:54:45 -0400 Subject: [PATCH 069/113] change TODO to note since calling .eval() in Eigen is valid --- gtsam/geometry/CameraSet.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/gtsam/geometry/CameraSet.h b/gtsam/geometry/CameraSet.h index 950747102..cf4beb883 100644 --- a/gtsam/geometry/CameraSet.h +++ b/gtsam/geometry/CameraSet.h @@ -438,8 +438,7 @@ class CameraSet : public std::vector> { // (DxD) += (DxZDim) * ( (ZDimxD) - (ZDimx3) * (3xZDim) * (ZDimxD) ) // add contribution of current factor - // TODO(gareth): Eigen doesn't let us pass the expression. Call eval() for - // now... + // Eigen doesn't let us pass the expression so we call eval() augmentedHessian.updateDiagonalBlock( aug_i, ((FiT * From 299e5fd42e0ce93d36c14e7e97c9d66c1d664905 Mon Sep 17 00:00:00 2001 From: Varun Agrawal Date: Mon, 18 Sep 2023 22:55:34 -0400 Subject: [PATCH 070/113] remove unnecessary push_back --- gtsam/hybrid/HybridNonlinearFactorGraph.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/gtsam/hybrid/HybridNonlinearFactorGraph.cpp b/gtsam/hybrid/HybridNonlinearFactorGraph.cpp index 2459e4ec9..e51adb9cd 100644 --- a/gtsam/hybrid/HybridNonlinearFactorGraph.cpp +++ b/gtsam/hybrid/HybridNonlinearFactorGraph.cpp @@ -56,8 +56,6 @@ HybridGaussianFactorGraph::shared_ptr HybridNonlinearFactorGraph::linearize( for (auto& f : factors_) { // First check if it is a valid factor if (!f) { - // TODO(dellaert): why? - linearFG->push_back(GaussianFactor::shared_ptr()); continue; } // Check if it is a nonlinear mixture factor From 8255ad596cecda7e9c9a6ee050d68d13df25c83c Mon Sep 17 00:00:00 2001 From: Varun Agrawal Date: Mon, 18 Sep 2023 22:57:24 -0400 Subject: [PATCH 071/113] add Testable traits to DiscreteBayesTree and HybridBayesTree --- gtsam/discrete/DiscreteBayesTree.h | 8 ++++++++ gtsam/hybrid/HybridBayesTree.h | 4 ++++ 2 files changed, 12 insertions(+) diff --git a/gtsam/discrete/DiscreteBayesTree.h b/gtsam/discrete/DiscreteBayesTree.h index 34f8f5d4a..89baf49a9 100644 --- a/gtsam/discrete/DiscreteBayesTree.h +++ b/gtsam/discrete/DiscreteBayesTree.h @@ -110,4 +110,12 @@ class GTSAM_EXPORT DiscreteBayesTree /// @} }; +/// traits +template <> +struct traits + : public Testable {}; + +template <> +struct traits : public Testable {}; + } // namespace gtsam diff --git a/gtsam/hybrid/HybridBayesTree.h b/gtsam/hybrid/HybridBayesTree.h index bc2a0f852..f91e16cbf 100644 --- a/gtsam/hybrid/HybridBayesTree.h +++ b/gtsam/hybrid/HybridBayesTree.h @@ -123,6 +123,10 @@ class GTSAM_EXPORT HybridBayesTree : public BayesTree { }; /// traits +template <> +struct traits : public Testable { +}; + template <> struct traits : public Testable {}; From 3779f5280d14f2700227e403d87f8189d018b5ad Mon Sep 17 00:00:00 2001 From: Varun Agrawal Date: Mon, 18 Sep 2023 22:57:59 -0400 Subject: [PATCH 072/113] use STL to make DecisionTreeFactor::combine more efficient --- gtsam/discrete/DecisionTreeFactor.cpp | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/gtsam/discrete/DecisionTreeFactor.cpp b/gtsam/discrete/DecisionTreeFactor.cpp index 17731453a..7202e6853 100644 --- a/gtsam/discrete/DecisionTreeFactor.cpp +++ b/gtsam/discrete/DecisionTreeFactor.cpp @@ -164,15 +164,23 @@ namespace gtsam { } // create new factor, note we collect keys that are not in frontalKeys - // TODO(frank): why do we need this??? result should contain correct keys!!! + /* + Due to branch merging, the labels in `result` may be missing some keys + E.g. After branch merging, we may get a ADT like: + Leaf [2] 1.0204082 + + This is missing the key values used for branching. + */ + KeyVector difference, frontalKeys_(frontalKeys), keys_(keys()); + // Get the difference of the frontalKeys and the factor keys using set_difference + std::sort(keys_.begin(), keys_.end()); + std::sort(frontalKeys_.begin(), frontalKeys_.end()); + std::set_difference(keys_.begin(), keys_.end(), frontalKeys_.begin(), + frontalKeys_.end(), back_inserter(difference)); + DiscreteKeys dkeys; - for (i = 0; i < keys().size(); i++) { - Key j = keys()[i]; - // TODO(frank): inefficient! - if (std::find(frontalKeys.begin(), frontalKeys.end(), j) != - frontalKeys.end()) - continue; - dkeys.push_back(DiscreteKey(j, cardinality(j))); + for (Key key : difference) { + dkeys.push_back(DiscreteKey(key, cardinality(key))); } return std::make_shared(dkeys, result); } From 3a34b79a554b24ef7eecd00cbd6ab9e93d55ec8c Mon Sep 17 00:00:00 2001 From: Varun Agrawal Date: Mon, 18 Sep 2023 23:02:36 -0400 Subject: [PATCH 073/113] improve docstring for HybridGaussianISAM --- gtsam/hybrid/HybridGaussianISAM.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/gtsam/hybrid/HybridGaussianISAM.h b/gtsam/hybrid/HybridGaussianISAM.h index 8578f3dc5..ba2de52d2 100644 --- a/gtsam/hybrid/HybridGaussianISAM.h +++ b/gtsam/hybrid/HybridGaussianISAM.h @@ -29,7 +29,8 @@ namespace gtsam { /** - * @brief + * @brief Incremental Smoothing and Mapping (ISAM) algorithm + * for hybrid factor graphs. * * @ingroup hybrid */ From 15c5a94e96fa3fb7f2d445d2c5e2fd19781b18fd Mon Sep 17 00:00:00 2001 From: Varun Agrawal Date: Mon, 18 Sep 2023 23:03:07 -0400 Subject: [PATCH 074/113] remove unused CMake variable --- gtsam/CMakeLists.txt | 4 ---- 1 file changed, 4 deletions(-) diff --git a/gtsam/CMakeLists.txt b/gtsam/CMakeLists.txt index 555e077b8..cb87a6bcd 100644 --- a/gtsam/CMakeLists.txt +++ b/gtsam/CMakeLists.txt @@ -145,10 +145,6 @@ if (GTSAM_USE_EIGEN_MKL) target_include_directories(gtsam PUBLIC ${MKL_INCLUDE_DIR}) endif() -if(GTSAM_USE_TBB) - target_include_directories(gtsam PUBLIC ${TBB_INCLUDE_DIRS}) -endif() - # Add includes for source directories 'BEFORE' boost and any system include # paths so that the compiler uses GTSAM headers in our source directory instead # of any previously installed GTSAM headers. From 066f53775a468a1bbd4b5aefa3f1ebba38022a1f Mon Sep 17 00:00:00 2001 From: Varun Agrawal Date: Mon, 18 Sep 2023 23:04:33 -0400 Subject: [PATCH 075/113] resolve test in testDiscreteMarginals --- gtsam/discrete/tests/testDiscreteMarginals.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gtsam/discrete/tests/testDiscreteMarginals.cpp b/gtsam/discrete/tests/testDiscreteMarginals.cpp index ad9b1b191..76bed2fd6 100644 --- a/gtsam/discrete/tests/testDiscreteMarginals.cpp +++ b/gtsam/discrete/tests/testDiscreteMarginals.cpp @@ -121,7 +121,7 @@ TEST_UNSAFE( DiscreteMarginals, truss ) { Clique expected0(std::make_shared((key[0] | key[2], key[4]) = "2/1 2/1 2/1 2/1")); Clique::shared_ptr actual0 = (*bayesTree)[0]; -// EXPECT(assert_equal(expected0, *actual0)); // TODO, correct but fails + EXPECT(assert_equal(expected0, *actual0)); Clique expected1(std::make_shared((key[1] | key[3], key[4]) = "1/2 1/2 1/2 1/2")); Clique::shared_ptr actual1 = (*bayesTree)[1]; From 2a386f8631e38de120d67138e6f2bfeb56d0f644 Mon Sep 17 00:00:00 2001 From: Varun Agrawal Date: Mon, 18 Sep 2023 23:04:53 -0400 Subject: [PATCH 076/113] imrpove bounds checks in Chebyshev2 --- gtsam/basis/Chebyshev2.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gtsam/basis/Chebyshev2.cpp b/gtsam/basis/Chebyshev2.cpp index 44876b6e9..63fca64cc 100644 --- a/gtsam/basis/Chebyshev2.cpp +++ b/gtsam/basis/Chebyshev2.cpp @@ -32,7 +32,7 @@ Weights Chebyshev2::CalculateWeights(size_t N, double x, double a, double b) { const double dj = x - Point(N, j, a, b); // only thing that depends on [a,b] - if (std::abs(dj) < 1e-10) { + if (std::abs(dj) < 1e-12) { // exceptional case: x coincides with a Chebyshev point weights.setZero(); weights(j) = 1; @@ -73,7 +73,7 @@ Weights Chebyshev2::DerivativeWeights(size_t N, double x, double a, double b) { for (size_t j = 0; j < N; j++) { const double dj = x - Point(N, j, a, b); // only thing that depends on [a,b] - if (std::abs(dj) < 1e-10) { + if (std::abs(dj) < 1e-12) { // exceptional case: x coincides with a Chebyshev point weightDerivatives.setZero(); // compute the jth row of the differentiation matrix for this point From b2efd64d2bfd8ab378dc8dd16faff971d82391b4 Mon Sep 17 00:00:00 2001 From: Varun Agrawal Date: Mon, 18 Sep 2023 23:05:25 -0400 Subject: [PATCH 077/113] overload the Chebyshev2::Point method to reduce duplication --- gtsam/basis/Chebyshev2.h | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/gtsam/basis/Chebyshev2.h b/gtsam/basis/Chebyshev2.h index 4c3542d56..55bb39488 100644 --- a/gtsam/basis/Chebyshev2.h +++ b/gtsam/basis/Chebyshev2.h @@ -51,27 +51,30 @@ class GTSAM_EXPORT Chebyshev2 : public Basis { using Parameters = Eigen::Matrix; using DiffMatrix = Eigen::Matrix; - /// Specific Chebyshev point - static double Point(size_t N, int j) { + /** + * @brief Specific Chebyshev point, within [a,b] interval. + * Default interval is [-1, 1] + * + * @param N The degree of the polynomial + * @param j The index of the Chebyshev point + * @param a Lower bound of interval (default: -1) + * @param b Upper bound of interval (default: 1) + * @return double + */ + static double Point(size_t N, int j, double a = -1, double b = 1) { assert(j >= 0 && size_t(j) < N); const double dtheta = M_PI / (N > 1 ? (N - 1) : 1); // We add -PI so that we get values ordered from -1 to +1 - // sin(- M_PI_2 + dtheta*j); also works - return cos(-M_PI + dtheta * j); - } - - /// Specific Chebyshev point, within [a,b] interval - static double Point(size_t N, int j, double a, double b) { - assert(j >= 0 && size_t(j) < N); - const double dtheta = M_PI / (N - 1); - // We add -PI so that we get values ordered from -1 to +1 + // sin(-M_PI_2 + dtheta*j); also works return a + (b - a) * (1. + cos(-M_PI + dtheta * j)) / 2; } /// All Chebyshev points static Vector Points(size_t N) { Vector points(N); - for (size_t j = 0; j < N; j++) points(j) = Point(N, j); + for (size_t j = 0; j < N; j++) { + points(j) = Point(N, j); + } return points; } From 7d903264a9f055efe62d7f7bf6f7d92f3faef351 Mon Sep 17 00:00:00 2001 From: ShuangLiu1992 Date: Wed, 20 Sep 2023 10:22:07 +0100 Subject: [PATCH 078/113] Fix precompiled_header.h on MSVC when boost is disabled --- gtsam/precompiled_header.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/gtsam/precompiled_header.h b/gtsam/precompiled_header.h index 5ff2a55c5..e7188fc05 100644 --- a/gtsam/precompiled_header.h +++ b/gtsam/precompiled_header.h @@ -42,8 +42,10 @@ #include #include #include +#ifdef GTSAM_ENABLE_BOOST_SERIALIZATION #include #include +#endif #include #include #include From c25f8a60b7d4de1cae2fd193a23b71ea7a79631f Mon Sep 17 00:00:00 2001 From: Varun Agrawal Date: Thu, 21 Sep 2023 13:56:20 -0400 Subject: [PATCH 079/113] fix warnings --- gtsam_unstable/partition/FindSeparator-inl.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gtsam_unstable/partition/FindSeparator-inl.h b/gtsam_unstable/partition/FindSeparator-inl.h index e991c5af2..f4718f7a9 100644 --- a/gtsam_unstable/partition/FindSeparator-inl.h +++ b/gtsam_unstable/partition/FindSeparator-inl.h @@ -39,7 +39,7 @@ namespace gtsam { namespace partition { * whether node j is in the left part of the graph, the right part, or the * separator, respectively */ - std::pair separatorMetis(idx_t n, const sharedInts& xadj, + std::pair separatorMetis(idx_t n, const sharedInts& xadj, const sharedInts& adjncy, const sharedInts& adjwgt, bool verbose) { // control parameters @@ -277,7 +277,7 @@ namespace gtsam { namespace partition { //throw runtime_error("separatorPartitionByMetis:stop for debug"); } - if(result.C.size() != sepsize) { + if(result.C.size() != size_t(sepsize)) { std::cout << "total key: " << keys.size() << " result(A,B,C) = " << result.A.size() << ", " << result.B.size() << ", " << result.C.size() << "; sepsize from Metis = " << sepsize << std::endl; From 331ae137af844fee6b7604dbddc0a71764fb2880 Mon Sep 17 00:00:00 2001 From: Varun Agrawal Date: Thu, 21 Sep 2023 15:55:58 -0400 Subject: [PATCH 080/113] re-enable debug CI for linux --- .github/workflows/build-linux.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-linux.yml b/.github/workflows/build-linux.yml index 5de09c63f..4502dbe0e 100644 --- a/.github/workflows/build-linux.yml +++ b/.github/workflows/build-linux.yml @@ -31,7 +31,7 @@ jobs: ubuntu-22.04-clang-14, ] - build_type: [Release] + build_type: [Debug, Release] build_unstable: [ON] include: - name: ubuntu-20.04-gcc-9 From 2c4cdbb8c186be0ed02af1b7a0434eae29455f79 Mon Sep 17 00:00:00 2001 From: Tal Regev Date: Thu, 7 Sep 2023 10:09:57 +0300 Subject: [PATCH 081/113] Fix windows tests --- .github/workflows/build-windows.yml | 7 +++++-- gtsam/discrete/AlgebraicDecisionTree.h | 2 +- gtsam/linear/SubgraphBuilder.h | 2 +- gtsam/sfm/TranslationRecovery.h | 2 +- gtsam_unstable/geometry/Event.h | 6 +++--- gtsam_unstable/partition/GenericGraph.h | 7 ++++--- gtsam_unstable/partition/tests/CMakeLists.txt | 2 +- gtsam_unstable/slam/ProjectionFactorPPPC.h | 2 +- .../slam/SmartProjectionPoseFactorRollingShutter.h | 2 +- .../slam/tests/testSmartStereoProjectionPoseFactor.cpp | 10 +++++----- 10 files changed, 23 insertions(+), 19 deletions(-) diff --git a/.github/workflows/build-windows.yml b/.github/workflows/build-windows.yml index a1d232b2a..f0568394f 100644 --- a/.github/workflows/build-windows.yml +++ b/.github/workflows/build-windows.yml @@ -150,7 +150,10 @@ jobs: # cmake --build build -j4 --config ${{ matrix.build_type }} --target check.dynamics_unstable # Compile. Fail with exception # cmake --build build -j4 --config ${{ matrix.build_type }} --target check.nonlinear_unstable - # Compilation error + # Compile. Fail with exception # cmake --build build -j4 --config ${{ matrix.build_type }} --target check.slam_unstable - # Compilation error + # Compile. Fail with exception # cmake --build build -j4 --config ${{ matrix.build_type }} --target check.partition + + # Run all tests + # cmake --build build -j1 --config ${{ matrix.build_type }} --target check diff --git a/gtsam/discrete/AlgebraicDecisionTree.h b/gtsam/discrete/AlgebraicDecisionTree.h index 20ab4bd68..9f55f3b63 100644 --- a/gtsam/discrete/AlgebraicDecisionTree.h +++ b/gtsam/discrete/AlgebraicDecisionTree.h @@ -36,7 +36,7 @@ namespace gtsam { * @ingroup discrete */ template - class GTSAM_EXPORT AlgebraicDecisionTree : public DecisionTree { + class AlgebraicDecisionTree : public DecisionTree { /** * @brief Default method used by `labelFormatter` or `valueFormatter` when * printing. diff --git a/gtsam/linear/SubgraphBuilder.h b/gtsam/linear/SubgraphBuilder.h index aafba9306..f9ddd4c9a 100644 --- a/gtsam/linear/SubgraphBuilder.h +++ b/gtsam/linear/SubgraphBuilder.h @@ -182,7 +182,7 @@ GaussianFactorGraph buildFactorSubgraph(const GaussianFactorGraph &gfg, /** Split the graph into a subgraph and the remaining edges. * Note that the remaining factorgraph has null factors. */ -std::pair splitFactorGraph( +std::pair GTSAM_EXPORT splitFactorGraph( const GaussianFactorGraph &factorGraph, const Subgraph &subgraph); } // namespace gtsam diff --git a/gtsam/sfm/TranslationRecovery.h b/gtsam/sfm/TranslationRecovery.h index 44a5ef43e..4848d7cfa 100644 --- a/gtsam/sfm/TranslationRecovery.h +++ b/gtsam/sfm/TranslationRecovery.h @@ -48,7 +48,7 @@ namespace gtsam { // where s is an arbitrary scale that can be supplied, default 1.0. Hence, two // versions are supplied below corresponding to whether we have initial values // or not. -class TranslationRecovery { +class GTSAM_EXPORT TranslationRecovery { public: using KeyPair = std::pair; using TranslationEdges = std::vector>; diff --git a/gtsam_unstable/geometry/Event.h b/gtsam_unstable/geometry/Event.h index a4055d038..a3c907646 100644 --- a/gtsam_unstable/geometry/Event.h +++ b/gtsam_unstable/geometry/Event.h @@ -34,7 +34,7 @@ namespace gtsam { * SLAM, where we have "time of arrival" measurements at a set of sensors. The * TOA functor below provides a measurement function for those applications. */ -class Event { +class GTSAM_UNSTABLE_EXPORT Event { double time_; ///< Time event was generated Point3 location_; ///< Location at time event was generated @@ -62,10 +62,10 @@ class Event { } /** print with optional string */ - GTSAM_UNSTABLE_EXPORT void print(const std::string& s = "") const; + void print(const std::string& s = "") const; /** equals with an tolerance */ - GTSAM_UNSTABLE_EXPORT bool equals(const Event& other, + bool equals(const Event& other, double tol = 1e-9) const; /// Updates a with tangent space delta diff --git a/gtsam_unstable/partition/GenericGraph.h b/gtsam_unstable/partition/GenericGraph.h index bcfd77336..a7f2c1ea3 100644 --- a/gtsam_unstable/partition/GenericGraph.h +++ b/gtsam_unstable/partition/GenericGraph.h @@ -14,6 +14,7 @@ #include #include #include +#include #include "PartitionWorkSpace.h" @@ -49,7 +50,7 @@ namespace gtsam { namespace partition { typedef std::vector GenericGraph2D; /** merge nodes in DSF using constraints captured by the given graph */ - std::list > findIslands(const GenericGraph2D& graph, const std::vector& keys, WorkSpace& workspace, + std::list > GTSAM_UNSTABLE_EXPORT findIslands(const GenericGraph2D& graph, const std::vector& keys, WorkSpace& workspace, const int minNrConstraintsPerCamera, const int minNrConstraintsPerLandmark); /** eliminate the sensors from generic graph */ @@ -97,11 +98,11 @@ namespace gtsam { namespace partition { typedef std::vector GenericGraph3D; /** merge nodes in DSF using constraints captured by the given graph */ - std::list > findIslands(const GenericGraph3D& graph, const std::vector& keys, WorkSpace& workspace, + std::list > GTSAM_UNSTABLE_EXPORT findIslands(const GenericGraph3D& graph, const std::vector& keys, WorkSpace& workspace, const size_t minNrConstraintsPerCamera, const size_t minNrConstraintsPerLandmark); /** eliminate the sensors from generic graph */ - void reduceGenericGraph(const GenericGraph3D& graph, const std::vector& cameraKeys, const std::vector& landmarkKeys, + void GTSAM_UNSTABLE_EXPORT reduceGenericGraph(const GenericGraph3D& graph, const std::vector& cameraKeys, const std::vector& landmarkKeys, const std::vector& dictionary, GenericGraph3D& reducedGraph); /** check whether the 3D graph is singular (under constrained) */ diff --git a/gtsam_unstable/partition/tests/CMakeLists.txt b/gtsam_unstable/partition/tests/CMakeLists.txt index 0b918e497..4ca6b9186 100644 --- a/gtsam_unstable/partition/tests/CMakeLists.txt +++ b/gtsam_unstable/partition/tests/CMakeLists.txt @@ -1,6 +1,6 @@ set(ignore_test "testNestedDissection.cpp") -if (NOT GTSAM_USE_BOOST_FEATURES) +if (NOT GTSAM_USE_BOOST_FEATURES OR MSVC) list(APPEND ignore_test "testFindSeparator.cpp") endif() diff --git a/gtsam_unstable/slam/ProjectionFactorPPPC.h b/gtsam_unstable/slam/ProjectionFactorPPPC.h index 5b6a83a33..df63330df 100644 --- a/gtsam_unstable/slam/ProjectionFactorPPPC.h +++ b/gtsam_unstable/slam/ProjectionFactorPPPC.h @@ -32,7 +32,7 @@ namespace gtsam { * @ingroup slam */ template -class GTSAM_UNSTABLE_EXPORT ProjectionFactorPPPC +class ProjectionFactorPPPC : public NoiseModelFactorN { protected: Point2 measured_; ///< 2D measurement diff --git a/gtsam_unstable/slam/SmartProjectionPoseFactorRollingShutter.h b/gtsam_unstable/slam/SmartProjectionPoseFactorRollingShutter.h index 4af0be751..4a2047ee1 100644 --- a/gtsam_unstable/slam/SmartProjectionPoseFactorRollingShutter.h +++ b/gtsam_unstable/slam/SmartProjectionPoseFactorRollingShutter.h @@ -42,7 +42,7 @@ namespace gtsam { * @ingroup slam */ template -class GTSAM_UNSTABLE_EXPORT SmartProjectionPoseFactorRollingShutter +class SmartProjectionPoseFactorRollingShutter : public SmartProjectionFactor { private: typedef SmartProjectionFactor Base; diff --git a/gtsam_unstable/slam/tests/testSmartStereoProjectionPoseFactor.cpp b/gtsam_unstable/slam/tests/testSmartStereoProjectionPoseFactor.cpp index 1eceb8061..872cd2dea 100644 --- a/gtsam_unstable/slam/tests/testSmartStereoProjectionPoseFactor.cpp +++ b/gtsam_unstable/slam/tests/testSmartStereoProjectionPoseFactor.cpp @@ -1441,14 +1441,14 @@ TEST( SmartStereoProjectionPoseFactor, HessianWithRotationNonDegenerate ) { std::shared_ptr hessianFactorRotTran = smartFactor->linearize(tranValues); - // Hessian is invariant to rotations and translations in the degenerate case - EXPECT( - assert_equal(hessianFactor->information(), + double error; #ifdef GTSAM_USE_EIGEN_MKL - hessianFactorRotTran->information(), 1e-5)); + error = 1e-5; #else - hessianFactorRotTran->information(), 1e-6)); + error = 1e-6; #endif + // Hessian is invariant to rotations and translations in the degenerate case + EXPECT(assert_equal(hessianFactor->information(), hessianFactorRotTran->information(), error)); } /* ************************************************************************* */ From aebff10f05dcad3e9b03d1a828fc1c86ac7c95f3 Mon Sep 17 00:00:00 2001 From: Varun Agrawal Date: Tue, 26 Sep 2023 17:24:40 -0400 Subject: [PATCH 082/113] wrap print for Sim2 and Sim3 --- gtsam/geometry/geometry.i | 2 ++ 1 file changed, 2 insertions(+) diff --git a/gtsam/geometry/geometry.i b/gtsam/geometry/geometry.i index 35c470eca..615e0eced 100644 --- a/gtsam/geometry/geometry.i +++ b/gtsam/geometry/geometry.i @@ -1082,6 +1082,7 @@ class Similarity2 { // Standard Interface bool equals(const gtsam::Similarity2& sim, double tol) const; + void print(const std::string& s) const; Matrix matrix() const; gtsam::Rot2& rotation(); gtsam::Point2& translation(); @@ -1105,6 +1106,7 @@ class Similarity3 { // Standard Interface bool equals(const gtsam::Similarity3& sim, double tol) const; + void print(const std::string& s) const; Matrix matrix() const; gtsam::Rot3& rotation(); gtsam::Point3& translation(); From e096af463d18f9ca7516a26fa9d9fd5171d268a5 Mon Sep 17 00:00:00 2001 From: Varun Agrawal Date: Tue, 26 Sep 2023 17:51:52 -0400 Subject: [PATCH 083/113] default value for string arg in print --- gtsam/geometry/Similarity2.h | 2 +- gtsam/geometry/Similarity3.h | 2 +- gtsam/geometry/geometry.i | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/gtsam/geometry/Similarity2.h b/gtsam/geometry/Similarity2.h index 3e04aa3a1..6bec716ea 100644 --- a/gtsam/geometry/Similarity2.h +++ b/gtsam/geometry/Similarity2.h @@ -74,7 +74,7 @@ class GTSAM_EXPORT Similarity2 : public LieGroup { bool operator==(const Similarity2& other) const; /// Print with optional string - void print(const std::string& s) const; + void print(const std::string& s = "") const; GTSAM_EXPORT friend std::ostream& operator<<(std::ostream& os, const Similarity2& p); diff --git a/gtsam/geometry/Similarity3.h b/gtsam/geometry/Similarity3.h index 69b401620..89b2e3e34 100644 --- a/gtsam/geometry/Similarity3.h +++ b/gtsam/geometry/Similarity3.h @@ -75,7 +75,7 @@ class GTSAM_EXPORT Similarity3 : public LieGroup { bool operator==(const Similarity3& other) const; /// Print with optional string - void print(const std::string& s) const; + void print(const std::string& s = "") const; GTSAM_EXPORT friend std::ostream& operator<<(std::ostream& os, const Similarity3& p); diff --git a/gtsam/geometry/geometry.i b/gtsam/geometry/geometry.i index 615e0eced..01161817b 100644 --- a/gtsam/geometry/geometry.i +++ b/gtsam/geometry/geometry.i @@ -1082,7 +1082,7 @@ class Similarity2 { // Standard Interface bool equals(const gtsam::Similarity2& sim, double tol) const; - void print(const std::string& s) const; + void print(const std::string& s = "") const; Matrix matrix() const; gtsam::Rot2& rotation(); gtsam::Point2& translation(); @@ -1106,7 +1106,7 @@ class Similarity3 { // Standard Interface bool equals(const gtsam::Similarity3& sim, double tol) const; - void print(const std::string& s) const; + void print(const std::string& s = "") const; Matrix matrix() const; gtsam::Rot3& rotation(); gtsam::Point3& translation(); From 4de8f149f82bb5a2b8d3ffa9b97cb11f0f94a076 Mon Sep 17 00:00:00 2001 From: Varun Agrawal Date: Thu, 28 Sep 2023 07:18:11 -0400 Subject: [PATCH 084/113] fix docstring --- python/gtsam/tests/test_backwards_compatibility.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/gtsam/tests/test_backwards_compatibility.py b/python/gtsam/tests/test_backwards_compatibility.py index 414b65e8c..ca96cdf57 100644 --- a/python/gtsam/tests/test_backwards_compatibility.py +++ b/python/gtsam/tests/test_backwards_compatibility.py @@ -29,7 +29,7 @@ UPRIGHT = Rot3.Ypr(-np.pi / 2, 0.0, -np.pi / 2) class TestBackwardsCompatibility(GtsamTestCase): - """Tests for the backwards compatibility for the Python wrapper.""" + """Tests for backwards compatibility of the Python wrapper.""" def setUp(self): """Setup test fixtures""" From fbdd602532842c48110cbfe3a612505d127737b3 Mon Sep 17 00:00:00 2001 From: Jose Luis Blanco-Claraco Date: Thu, 28 Sep 2023 16:58:00 +0200 Subject: [PATCH 085/113] Fix usage of OptionalNone in a different namespace --- gtsam/nonlinear/NonlinearFactor.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gtsam/nonlinear/NonlinearFactor.h b/gtsam/nonlinear/NonlinearFactor.h index 57c4a00eb..725117748 100644 --- a/gtsam/nonlinear/NonlinearFactor.h +++ b/gtsam/nonlinear/NonlinearFactor.h @@ -46,7 +46,7 @@ namespace gtsam { * Had to use the static_cast of a nullptr, because the compiler is not able to * deduce the type of the nullptr when expanding the evaluateError templates. */ -#define OptionalNone static_cast(nullptr) +#define OptionalNone static_cast(nullptr) /** This typedef will be used everywhere boost::optional reference was used * previously. This is used to indicate that the Jacobian is optional. In the future From 87158154ada5e094f8aa65773522723134348b0e Mon Sep 17 00:00:00 2001 From: Steve Oswald <82703776+stepeos@users.noreply.github.com> Date: Sun, 1 Oct 2023 22:32:26 +0200 Subject: [PATCH 086/113] Update INSTALL.md correct usage of march native compile option --- INSTALL.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/INSTALL.md b/INSTALL.md index f148e3718..10bee196c 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -182,7 +182,7 @@ Here are some tips to get the best possible performance out of GTSAM. optimization by 30-50%. Please note that this may not be true for very small problems where the overhead of dispatching work to multiple threads outweighs the benefit. We recommend that you benchmark your problem with/without TBB. -3. Add `-march=native` to `GTSAM_CMAKE_CXX_FLAGS`. A performance gain of +3. Use `GTSAM_BUILD_WITH_MARCH_NATIVE`. A performance gain of 25-30% can be expected on modern processors. Note that this affects the portability of your executable. It may not run when copied to another system with older/different processor architecture. From b1fab94680bbc059c2f1b575d42f937249405301 Mon Sep 17 00:00:00 2001 From: Varun Agrawal Date: Mon, 2 Oct 2023 14:27:20 -0400 Subject: [PATCH 087/113] handle Symbol vs symbol on case insensitive file systems --- matlab/CMakeLists.txt | 4 ++++ matlab/README.md | 8 ++++++++ 2 files changed, 12 insertions(+) diff --git a/matlab/CMakeLists.txt b/matlab/CMakeLists.txt index 45d56e41d..e38d7cb6f 100644 --- a/matlab/CMakeLists.txt +++ b/matlab/CMakeLists.txt @@ -64,6 +64,10 @@ set(ignore gtsam::Point3 gtsam::CustomFactor) +if (APPLE OR WIN32) + list(APPEND ignore gtsam::Symbol) +endif() + set(interface_files ${GTSAM_SOURCE_DIR}/gtsam/gtsam.i ${GTSAM_SOURCE_DIR}/gtsam/base/base.i diff --git a/matlab/README.md b/matlab/README.md index 25628b633..ac0956cb7 100644 --- a/matlab/README.md +++ b/matlab/README.md @@ -10,6 +10,14 @@ The interface generated by the wrap tool also redirects the standard output stre For instructions on updating the version of the [wrap library](https://github.com/borglab/wrap) included in GTSAM to the latest version, please refer to the [wrap README](https://github.com/borglab/wrap/blob/master/README.md#git-subtree-and-contributing) +### Filename Case Sensitivity + +Due to the file name syntax requirements of Matlab, having the files `Symbol.m` (for the class) and `symbol.m` (for the function) together on an OS with a case-insensitive filesystem is impossible. + +To put it simply, for an OS like Windows or MacOS where the filesystem is case-insensitive, we cannot have two files `symbol.m` and `Symbol.m` in the same folder. When trying to write one file after another, they will end up overwriting each other. We cannot specify 2 different filenames, since Matlab's syntax prevents that. + +For this reason, on Windows and MacOS, we ignore the `Symbol` class and request users to use the `symbol` function exclusively for key creation. + ## Ubuntu If you have a newer Ubuntu system (later than 10.04), you must make a small modification to your MATLAB installation, due to MATLAB being distributed with an old version of the C++ standard library. Delete or rename all files starting with `libstdc++` in your MATLAB installation directory, in paths: From 79983285835c6b2f21ee5f7bd62026aba76825e7 Mon Sep 17 00:00:00 2001 From: Varun Agrawal Date: Thu, 5 Oct 2023 09:59:04 -0400 Subject: [PATCH 088/113] use Pose3Pairs for Similarity3::Align --- gtsam/geometry/Similarity3.cpp | 2 +- gtsam/geometry/Similarity3.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/gtsam/geometry/Similarity3.cpp b/gtsam/geometry/Similarity3.cpp index a9a369e44..cf2360b08 100644 --- a/gtsam/geometry/Similarity3.cpp +++ b/gtsam/geometry/Similarity3.cpp @@ -171,7 +171,7 @@ Similarity3 Similarity3::Align(const Point3Pairs &abPointPairs) { return internal::align(d_abPointPairs, aRb, centroids); } -Similarity3 Similarity3::Align(const vector &abPosePairs) { +Similarity3 Similarity3::Align(const Pose3Pairs &abPosePairs) { const size_t n = abPosePairs.size(); if (n < 2) throw std::runtime_error("input should have at least 2 pairs of poses"); diff --git a/gtsam/geometry/Similarity3.h b/gtsam/geometry/Similarity3.h index 89b2e3e34..cd4af89bc 100644 --- a/gtsam/geometry/Similarity3.h +++ b/gtsam/geometry/Similarity3.h @@ -133,7 +133,7 @@ class GTSAM_EXPORT Similarity3 : public LieGroup { * computed using the algorithm described here: * http://www5.informatik.uni-erlangen.de/Forschung/Publikationen/2005/Zinsser05-PSR.pdf */ - static Similarity3 Align(const std::vector& abPosePairs); + static Similarity3 Align(const Pose3Pairs& abPosePairs); /// @} /// @name Lie Group From c7abbad0bc2a43538edfe8c3e23447662cfc49df Mon Sep 17 00:00:00 2001 From: Varun Agrawal Date: Fri, 6 Oct 2023 12:21:06 -0400 Subject: [PATCH 089/113] Squashed 'wrap/' changes from 2f136936d..bd57210d9 bd57210d9 Merge pull request #162 from borglab/matlab-updates 1467cf23a small improvements to Matlab wrapper ce4d872c3 support for Apple Silicon 246c723e2 Merge pull request #161 from borglab/fix-matlab-enum 4a4ddb912 allow optional std:: for pair 390cb8092 Merge pull request #160 from borglab/upgrade-pybind11 c48bfa418 upgrade pybind11 to v2.11.1 c941bdd48 update gitignore git-subtree-dir: wrap git-subtree-split: bd57210d9aa620babbffe7eeb43abd77fea641e3 --- .gitignore | 2 + cmake/MatlabWrap.cmake | 21 +- gtwrap/interface_parser/function.py | 7 +- gtwrap/matlab_wrapper/wrapper.py | 9 +- pybind11/.clang-tidy | 2 + pybind11/.codespell-ignore-lines | 24 + pybind11/.github/CONTRIBUTING.md | 4 +- .../.github/ISSUE_TEMPLATE/bug-report.yml | 22 +- pybind11/.github/workflows/ci.yml | 291 ++++++- pybind11/.github/workflows/configure.yml | 26 +- pybind11/.github/workflows/format.yml | 9 +- pybind11/.github/workflows/labeler.yml | 11 +- pybind11/.github/workflows/pip.yml | 12 +- pybind11/.github/workflows/upstream.yml | 78 +- pybind11/.gitignore | 1 + pybind11/.pre-commit-config.yaml | 157 ++-- pybind11/CMakeLists.txt | 33 +- pybind11/MANIFEST.in | 3 +- pybind11/README.rst | 2 +- pybind11/SECURITY.md | 13 + pybind11/docs/advanced/cast/custom.rst | 4 +- pybind11/docs/advanced/cast/stl.rst | 6 +- pybind11/docs/advanced/cast/strings.rst | 10 +- pybind11/docs/advanced/classes.rst | 4 +- pybind11/docs/advanced/embedding.rst | 2 +- pybind11/docs/advanced/exceptions.rst | 9 +- pybind11/docs/advanced/misc.rst | 77 +- pybind11/docs/advanced/pycpp/numpy.rst | 4 +- pybind11/docs/advanced/smart_ptrs.rst | 2 +- pybind11/docs/changelog.rst | 358 ++++++++ pybind11/docs/classes.rst | 14 + pybind11/docs/compiling.rst | 9 +- pybind11/docs/conf.py | 3 +- pybind11/docs/faq.rst | 3 +- pybind11/docs/release.rst | 10 +- pybind11/docs/upgrade.rst | 14 + pybind11/include/pybind11/attr.h | 18 +- pybind11/include/pybind11/buffer_info.h | 17 +- pybind11/include/pybind11/cast.h | 99 ++- pybind11/include/pybind11/detail/class.h | 59 +- pybind11/include/pybind11/detail/common.h | 178 +++- pybind11/include/pybind11/detail/descr.h | 13 + pybind11/include/pybind11/detail/init.h | 40 +- pybind11/include/pybind11/detail/internals.h | 124 ++- .../pybind11/detail/type_caster_base.h | 219 ++++- pybind11/include/pybind11/eigen.h | 698 +-------------- pybind11/include/pybind11/eigen/common.h | 9 + pybind11/include/pybind11/eigen/matrix.h | 714 ++++++++++++++++ pybind11/include/pybind11/eigen/tensor.h | 516 +++++++++++ pybind11/include/pybind11/embed.h | 131 ++- pybind11/include/pybind11/functional.h | 13 +- pybind11/include/pybind11/gil.h | 63 +- pybind11/include/pybind11/numpy.h | 48 +- pybind11/include/pybind11/operators.h | 1 + pybind11/include/pybind11/options.h | 16 + pybind11/include/pybind11/pybind11.h | 188 ++-- pybind11/include/pybind11/pytypes.h | 337 ++++++-- pybind11/include/pybind11/stl.h | 50 +- pybind11/include/pybind11/stl_bind.h | 176 ++-- .../pybind11/type_caster_pyobject_ptr.h | 61 ++ pybind11/noxfile.py | 18 +- pybind11/pybind11/__init__.py | 5 +- pybind11/pybind11/__main__.py | 17 +- pybind11/pybind11/_version.py | 2 +- pybind11/pybind11/commands.py | 14 +- pybind11/pybind11/setup_helpers.py | 22 +- pybind11/pyproject.toml | 49 +- pybind11/setup.cfg | 9 +- pybind11/setup.py | 3 +- pybind11/tests/CMakeLists.txt | 48 +- pybind11/tests/conftest.py | 76 +- pybind11/tests/constructor_stats.h | 2 +- pybind11/tests/cross_module_gil_utils.cpp | 67 +- .../tests/eigen_tensor_avoid_stl_array.cpp | 14 + pybind11/tests/env.py | 3 +- .../tests/extra_python_package/test_files.py | 137 +-- pybind11/tests/pybind11_tests.cpp | 6 + pybind11/tests/requirements.txt | 2 +- pybind11/tests/test_async.py | 4 +- pybind11/tests/test_buffers.cpp | 35 + pybind11/tests/test_buffers.py | 60 +- pybind11/tests/test_builtin_casters.cpp | 18 +- pybind11/tests/test_builtin_casters.py | 10 +- pybind11/tests/test_callbacks.cpp | 37 + pybind11/tests/test_callbacks.py | 27 +- pybind11/tests/test_chrono.py | 4 - pybind11/tests/test_class.cpp | 48 +- pybind11/tests/test_class.py | 24 +- .../tests/test_cmake_build/CMakeLists.txt | 3 - .../installed_embed/CMakeLists.txt | 8 +- .../installed_function/CMakeLists.txt | 8 +- .../installed_target/CMakeLists.txt | 8 +- .../subdirectory_embed/CMakeLists.txt | 8 +- .../subdirectory_function/CMakeLists.txt | 8 +- .../subdirectory_target/CMakeLists.txt | 8 +- pybind11/tests/test_const_name.py | 4 +- .../tests/test_constants_and_functions.cpp | 37 +- .../tests/test_constants_and_functions.py | 4 + pybind11/tests/test_copy_move.cpp | 238 ++++++ pybind11/tests/test_custom_type_casters.cpp | 14 +- pybind11/tests/test_custom_type_casters.py | 6 +- pybind11/tests/test_custom_type_setup.py | 2 +- pybind11/tests/test_docstring_options.cpp | 53 ++ pybind11/tests/test_docstring_options.py | 23 + pybind11/tests/test_eigen_matrix.cpp | 428 ++++++++++ pybind11/tests/test_eigen_matrix.py | 807 ++++++++++++++++++ pybind11/tests/test_eigen_tensor.cpp | 18 + pybind11/tests/test_eigen_tensor.inl | 333 ++++++++ pybind11/tests/test_eigen_tensor.py | 288 +++++++ pybind11/tests/test_embed/catch.cpp | 22 +- .../tests/test_embed/test_interpreter.cpp | 123 ++- pybind11/tests/test_enum.py | 2 + pybind11/tests/test_exceptions.cpp | 20 +- pybind11/tests/test_exceptions.py | 87 +- pybind11/tests/test_factory_constructors.py | 2 +- pybind11/tests/test_gil_scoped.cpp | 107 ++- pybind11/tests/test_gil_scoped.py | 225 ++++- pybind11/tests/test_iostream.py | 78 +- pybind11/tests/test_kwargs_and_defaults.cpp | 10 +- pybind11/tests/test_kwargs_and_defaults.py | 31 +- pybind11/tests/test_local_bindings.py | 3 +- .../tests/test_methods_and_attributes.cpp | 34 + pybind11/tests/test_methods_and_attributes.py | 18 +- pybind11/tests/test_modules.py | 19 +- pybind11/tests/test_numpy_array.cpp | 28 + pybind11/tests/test_numpy_array.py | 95 ++- pybind11/tests/test_numpy_dtypes.py | 28 +- pybind11/tests/test_numpy_vectorize.py | 2 +- pybind11/tests/test_operator_overloading.cpp | 23 +- pybind11/tests/test_operator_overloading.py | 1 - pybind11/tests/test_pytypes.cpp | 66 +- pybind11/tests/test_pytypes.py | 187 +++- .../tests/test_sequences_and_iterators.cpp | 19 + .../tests/test_sequences_and_iterators.py | 13 +- pybind11/tests/test_smart_ptr.cpp | 4 +- pybind11/tests/test_stl.cpp | 22 +- pybind11/tests/test_stl.py | 16 +- pybind11/tests/test_stl_binders.cpp | 44 + pybind11/tests/test_stl_binders.py | 56 +- pybind11/tests/test_tagbased_polymorphic.cpp | 4 +- .../tests/test_type_caster_pyobject_ptr.cpp | 130 +++ .../tests/test_type_caster_pyobject_ptr.py | 104 +++ pybind11/tests/test_unnamed_namespace_a.cpp | 38 + pybind11/tests/test_unnamed_namespace_a.py | 34 + pybind11/tests/test_unnamed_namespace_b.cpp | 13 + pybind11/tests/test_unnamed_namespace_b.py | 5 + .../tests/test_vector_unique_ptr_member.cpp | 54 ++ .../tests/test_vector_unique_ptr_member.py | 14 + pybind11/tests/test_virtual_functions.cpp | 3 +- pybind11/tests/test_virtual_functions.py | 7 +- pybind11/tools/FindCatch.cmake | 8 +- pybind11/tools/FindPythonLibsNew.cmake | 12 +- pybind11/tools/JoinPaths.cmake | 23 + .../codespell_ignore_lines_from_errors.py | 39 + pybind11/tools/make_changelog.py | 9 +- pybind11/tools/pybind11.pc.in | 7 + pybind11/tools/pybind11Common.cmake | 36 +- pybind11/tools/pybind11Config.cmake.in | 4 +- pybind11/tools/pybind11NewTools.cmake | 6 +- pybind11/tools/pybind11Tools.cmake | 16 +- pybind11/tools/setup_global.py.in | 6 +- pybind11/tools/setup_main.py.in | 4 + tests/expected/matlab/TemplatedFunctionRot3.m | 2 +- tests/expected/matlab/functions_wrapper.cpp | 16 +- tests/expected/python/functions_pybind.cpp | 1 + tests/fixtures/functions.i | 4 + 166 files changed, 8168 insertions(+), 1927 deletions(-) create mode 100644 pybind11/.codespell-ignore-lines create mode 100644 pybind11/SECURITY.md create mode 100644 pybind11/include/pybind11/eigen/common.h create mode 100644 pybind11/include/pybind11/eigen/matrix.h create mode 100644 pybind11/include/pybind11/eigen/tensor.h create mode 100644 pybind11/include/pybind11/type_caster_pyobject_ptr.h create mode 100644 pybind11/tests/eigen_tensor_avoid_stl_array.cpp create mode 100644 pybind11/tests/test_eigen_matrix.cpp create mode 100644 pybind11/tests/test_eigen_matrix.py create mode 100644 pybind11/tests/test_eigen_tensor.cpp create mode 100644 pybind11/tests/test_eigen_tensor.inl create mode 100644 pybind11/tests/test_eigen_tensor.py create mode 100644 pybind11/tests/test_type_caster_pyobject_ptr.cpp create mode 100644 pybind11/tests/test_type_caster_pyobject_ptr.py create mode 100644 pybind11/tests/test_unnamed_namespace_a.cpp create mode 100644 pybind11/tests/test_unnamed_namespace_a.py create mode 100644 pybind11/tests/test_unnamed_namespace_b.cpp create mode 100644 pybind11/tests/test_unnamed_namespace_b.py create mode 100644 pybind11/tests/test_vector_unique_ptr_member.cpp create mode 100644 pybind11/tests/test_vector_unique_ptr_member.py create mode 100644 pybind11/tools/JoinPaths.cmake create mode 100644 pybind11/tools/codespell_ignore_lines_from_errors.py create mode 100644 pybind11/tools/pybind11.pc.in diff --git a/.gitignore b/.gitignore index 9f79deafa..de16b118d 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,8 @@ __pycache__/ *dist* *.egg-info +**/.DS_Store + # Files related to code coverage stats **/.coverage diff --git a/cmake/MatlabWrap.cmake b/cmake/MatlabWrap.cmake index c45d8c050..55b7cdb99 100644 --- a/cmake/MatlabWrap.cmake +++ b/cmake/MatlabWrap.cmake @@ -105,7 +105,12 @@ function(wrap_library_internal interfaceHeader moduleName linkLibraries extraInc set(mexModuleExt mexglx) endif() elseif(APPLE) - set(mexModuleExt mexmaci64) + check_cxx_compiler_flag("-arch arm64" arm64Supported) + if (arm64Supported) + set(mexModuleExt mexmaca64) + else() + set(mexModuleExt mexmaci64) + endif() elseif(MSVC) if(CMAKE_CL_64) set(mexModuleExt mexw64) @@ -299,7 +304,12 @@ function(wrap_library_internal interfaceHeader moduleName linkLibraries extraInc APPEND PROPERTY COMPILE_FLAGS "/bigobj") elseif(APPLE) - set(mxLibPath "${MATLAB_ROOT}/bin/maci64") + check_cxx_compiler_flag("-arch arm64" arm64Supported) + if (arm64Supported) + set(mxLibPath "${MATLAB_ROOT}/bin/maca64") + else() + set(mxLibPath "${MATLAB_ROOT}/bin/maci64") + endif() target_link_libraries( ${moduleName}_matlab_wrapper "${mxLibPath}/libmex.dylib" "${mxLibPath}/libmx.dylib" "${mxLibPath}/libmat.dylib") @@ -367,7 +377,12 @@ function(check_conflicting_libraries_internal libraries) if(UNIX) # Set path for matlab's built-in libraries if(APPLE) - set(mxLibPath "${MATLAB_ROOT}/bin/maci64") + check_cxx_compiler_flag("-arch arm64" arm64Supported) + if (arm64Supported) + set(mxLibPath "${MATLAB_ROOT}/bin/maca64") + else() + set(mxLibPath "${MATLAB_ROOT}/bin/maci64") + endif() else() if(CMAKE_CL_64) set(mxLibPath "${MATLAB_ROOT}/bin/glnxa64") diff --git a/gtwrap/interface_parser/function.py b/gtwrap/interface_parser/function.py index b3f5d3227..b40884488 100644 --- a/gtwrap/interface_parser/function.py +++ b/gtwrap/interface_parser/function.py @@ -12,7 +12,8 @@ Author: Duy Nguyen Ta, Fan Jiang, Matthew Sklar, Varun Agrawal, and Frank Dellae from typing import Any, Iterable, List, Union -from pyparsing import Optional, ParseResults, delimitedList # type: ignore +from pyparsing import (Literal, Optional, ParseResults, # type: ignore + delimitedList) from .template import Template from .tokens import (COMMA, DEFAULT_ARG, EQUAL, IDENT, LOPBRACK, LPAREN, PAIR, @@ -105,8 +106,10 @@ class ReturnType: The return type can either be a single type or a pair such as . """ + # rule to parse optional std:: in front of `pair` + optional_std = Optional(Literal('std::')).suppress() _pair = ( - PAIR.suppress() # + optional_std + PAIR.suppress() # + LOPBRACK # + Type.rule("type1") # + COMMA # diff --git a/gtwrap/matlab_wrapper/wrapper.py b/gtwrap/matlab_wrapper/wrapper.py index 146209c44..9cd367cf3 100755 --- a/gtwrap/matlab_wrapper/wrapper.py +++ b/gtwrap/matlab_wrapper/wrapper.py @@ -1284,7 +1284,8 @@ class MatlabWrapper(CheckMixin, FormatMixin): [instantiated_class.name]) else: # Get the full namespace - class_name = ".".join(instantiated_class.parent.full_namespaces()[1:]) + class_name = ".".join( + instantiated_class.parent.full_namespaces()[1:]) if class_name != "": class_name += '.' @@ -1860,6 +1861,7 @@ class MatlabWrapper(CheckMixin, FormatMixin): """ for c in cc_content: if isinstance(c, list): + # c is a namespace if len(c) == 0: continue @@ -1875,6 +1877,7 @@ class MatlabWrapper(CheckMixin, FormatMixin): self.generate_content(sub_content[1], path_to_folder) elif isinstance(c[1], list): + # c is a wrapped function path_to_folder = osp.join(path, c[0]) if not osp.isdir(path_to_folder): @@ -1882,11 +1885,13 @@ class MatlabWrapper(CheckMixin, FormatMixin): os.makedirs(path_to_folder, exist_ok=True) except OSError: pass + for sub_content in c[1]: path_to_file = osp.join(path_to_folder, sub_content[0]) with open(path_to_file, 'w') as f: f.write(sub_content[1]) else: + # c is a wrapped class path_to_file = osp.join(path, c[0]) if not osp.isdir(path_to_file): @@ -1921,6 +1926,8 @@ class MatlabWrapper(CheckMixin, FormatMixin): for module in modules.values(): # Wrap the full namespace self.wrap_namespace(module) + + # Generate the wrapping code (both C++ and .m files) self.generate_wrapper(module) # Generate the corresponding .m and .cpp files diff --git a/pybind11/.clang-tidy b/pybind11/.clang-tidy index d945a4a27..23018386c 100644 --- a/pybind11/.clang-tidy +++ b/pybind11/.clang-tidy @@ -63,6 +63,8 @@ Checks: | -bugprone-unused-raii, CheckOptions: +- key: modernize-use-equals-default.IgnoreMacros + value: false - key: performance-for-range-copy.WarnOnAllAutoCopies value: true - key: performance-inefficient-string-concatenation.StrictMode diff --git a/pybind11/.codespell-ignore-lines b/pybind11/.codespell-ignore-lines new file mode 100644 index 000000000..2a01d63eb --- /dev/null +++ b/pybind11/.codespell-ignore-lines @@ -0,0 +1,24 @@ +template + template + auto &this_ = static_cast(*this); + if (load_impl(temp, false)) { + ssize_t nd = 0; + auto trivial = broadcast(buffers, nd, shape); + auto ndim = (size_t) nd; + int nd; + ssize_t ndim() const { return detail::array_proxy(m_ptr)->nd; } + using op = op_impl; +template + template + class_ &def(const detail::op_ &op, const Extra &...extra) { + class_ &def_cast(const detail::op_ &op, const Extra &...extra) { +@pytest.mark.parametrize("access", ["ro", "rw", "static_ro", "static_rw"]) +struct IntStruct { + explicit IntStruct(int v) : value(v){}; + ~IntStruct() { value = -value; } + IntStruct(const IntStruct &) = default; + IntStruct &operator=(const IntStruct &) = default; + py::class_(m, "IntStruct").def(py::init([](const int i) { return IntStruct(i); })); + py::implicitly_convertible(); + m.def("test", [](int expected, const IntStruct &in) { + [](int expected, const IntStruct &in) { diff --git a/pybind11/.github/CONTRIBUTING.md b/pybind11/.github/CONTRIBUTING.md index 00b1fea4c..ad7974395 100644 --- a/pybind11/.github/CONTRIBUTING.md +++ b/pybind11/.github/CONTRIBUTING.md @@ -235,8 +235,8 @@ directory inside your pybind11 git clone. Files will be modified in place, so you can use git to monitor the changes. ```bash -docker run --rm -v $PWD:/mounted_pybind11 -it silkeh/clang:13 -apt-get update && apt-get install -y python3-dev python3-pytest +docker run --rm -v $PWD:/mounted_pybind11 -it silkeh/clang:15-bullseye +apt-get update && apt-get install -y git python3-dev python3-pytest cmake -S /mounted_pybind11/ -B build -DCMAKE_CXX_CLANG_TIDY="$(which clang-tidy);--use-color" -DDOWNLOAD_EIGEN=ON -DDOWNLOAD_CATCH=ON -DCMAKE_CXX_STANDARD=17 cmake --build build -j 2 ``` diff --git a/pybind11/.github/ISSUE_TEMPLATE/bug-report.yml b/pybind11/.github/ISSUE_TEMPLATE/bug-report.yml index bd6a9a8e2..4f1e78f33 100644 --- a/pybind11/.github/ISSUE_TEMPLATE/bug-report.yml +++ b/pybind11/.github/ISSUE_TEMPLATE/bug-report.yml @@ -6,7 +6,8 @@ body: - type: markdown attributes: value: | - Maintainers will only make a best effort to triage PRs. Please do your best to make the issue as easy to act on as possible, and only open if clearly a problem with pybind11 (ask first if unsure). + Please do your best to make the issue as easy to act on as possible, and only submit here if there is clearly a problem with pybind11 (ask first if unsure). **Note that a reproducer in a PR is much more likely to get immediate attention.** + - type: checkboxes id: steps attributes: @@ -20,6 +21,13 @@ body: - label: Consider asking first in the [Gitter chat room](https://gitter.im/pybind/Lobby) or in a [Discussion](https:/pybind/pybind11/discussions/new). required: false + - type: input + id: version + attributes: + label: What version (or hash if on master) of pybind11 are you using? + validations: + required: true + - type: textarea id: description attributes: @@ -40,6 +48,14 @@ body: The code should be minimal, have no external dependencies, isolate the function(s) that cause breakage. Submit matched and complete C++ and Python snippets that can be easily compiled and run to diagnose the - issue. If possible, make a PR with a new, failing test to give us a - starting point to work on! + issue. — Note that a reproducer in a PR is much more likely to get + immediate attention: failing tests in the pybind11 CI are the best + starting point for working out fixes. render: text + + - type: input + id: regression + attributes: + label: Is this a regression? Put the last known working version here if it is. + description: Put the last known working version here if this is a regression. + value: Not a regression diff --git a/pybind11/.github/workflows/ci.yml b/pybind11/.github/workflows/ci.yml index 412282a4f..48f7c5e93 100644 --- a/pybind11/.github/workflows/ci.yml +++ b/pybind11/.github/workflows/ci.yml @@ -9,14 +9,19 @@ on: - stable - v* +permissions: read-all + concurrency: group: test-${{ github.ref }} cancel-in-progress: true env: + PIP_BREAK_SYSTEM_PACKAGES: 1 PIP_ONLY_BINARY: numpy FORCE_COLOR: 3 PYTEST_TIMEOUT: 300 + # For cmake: + VERBOSE: 1 jobs: # This is the "main" test suite, which tests a large number of different @@ -25,15 +30,16 @@ jobs: strategy: fail-fast: false matrix: - runs-on: [ubuntu-latest, windows-2022, macos-latest] + runs-on: [ubuntu-20.04, windows-2022, macos-latest] python: - '3.6' - '3.9' - '3.10' - - '3.11-dev' - - 'pypy-3.7' + - '3.11' + - '3.12' - 'pypy-3.8' - 'pypy-3.9' + - 'pypy-3.10' # Items in here will either be added to the build matrix (if not # present), or add new keys to an existing matrix element if all the @@ -42,12 +48,12 @@ jobs: # We support an optional key: args, for cmake args include: # Just add a key - - runs-on: ubuntu-latest + - runs-on: ubuntu-20.04 python: '3.6' args: > -DPYBIND11_FINDPYTHON=ON -DCMAKE_CXX_FLAGS="-D_=1" - - runs-on: ubuntu-latest + - runs-on: ubuntu-20.04 python: 'pypy-3.8' args: > -DPYBIND11_FINDPYTHON=ON @@ -69,6 +75,7 @@ jobs: uses: actions/setup-python@v4 with: python-version: ${{ matrix.python }} + allow-prereleases: true - name: Setup Boost (Linux) # Can't use boost + define _ @@ -80,7 +87,7 @@ jobs: run: brew install boost - name: Update CMake - uses: jwlawson/actions-setup-cmake@v1.12 + uses: jwlawson/actions-setup-cmake@v1.14 - name: Cache wheels if: runner.os == 'macOS' @@ -102,10 +109,12 @@ jobs: run: python -m pip install pytest-github-actions-annotate-failures # First build - C++11 mode and inplace + # More-or-less randomly adding -DPYBIND11_SIMPLE_GIL_MANAGEMENT=ON here. - name: Configure C++11 ${{ matrix.args }} run: > cmake -S . -B . -DPYBIND11_WERROR=ON + -DPYBIND11_SIMPLE_GIL_MANAGEMENT=ON -DDOWNLOAD_CATCH=ON -DDOWNLOAD_EIGEN=ON -DCMAKE_CXX_STANDARD=11 @@ -119,7 +128,7 @@ jobs: - name: C++11 tests # TODO: Figure out how to load the DLL on Python 3.8+ - if: "!(runner.os == 'Windows' && (matrix.python == 3.8 || matrix.python == 3.9 || matrix.python == '3.10' || matrix.python == '3.11-dev' || matrix.python == 'pypy-3.8'))" + if: "!(runner.os == 'Windows' && (matrix.python == 3.8 || matrix.python == 3.9 || matrix.python == '3.10' || matrix.python == '3.11' || matrix.python == 'pypy-3.8'))" run: cmake --build . --target cpptest -j 2 - name: Interface test C++11 @@ -129,10 +138,12 @@ jobs: run: git clean -fdx # Second build - C++17 mode and in a build directory + # More-or-less randomly adding -DPYBIND11_SIMPLE_GIL_MANAGEMENT=OFF here. - name: Configure C++17 run: > cmake -S . -B build2 -DPYBIND11_WERROR=ON + -DPYBIND11_SIMPLE_GIL_MANAGEMENT=OFF -DDOWNLOAD_CATCH=ON -DDOWNLOAD_EIGEN=ON -DCMAKE_CXX_STANDARD=17 @@ -146,7 +157,7 @@ jobs: - name: C++ tests # TODO: Figure out how to load the DLL on Python 3.8+ - if: "!(runner.os == 'Windows' && (matrix.python == 3.8 || matrix.python == 3.9 || matrix.python == '3.10' || matrix.python == '3.11-dev' || matrix.python == 'pypy-3.8'))" + if: "!(runner.os == 'Windows' && (matrix.python == 3.8 || matrix.python == 3.9 || matrix.python == '3.10' || matrix.python == '3.11' || matrix.python == 'pypy-3.8'))" run: cmake --build build2 --target cpptest # Third build - C++17 mode with unstable ABI @@ -158,7 +169,6 @@ jobs: -DDOWNLOAD_EIGEN=ON -DCMAKE_CXX_STANDARD=17 -DPYBIND11_INTERNALS_VERSION=10000000 - "-DPYBIND11_TEST_OVERRIDE=test_call_policies.cpp;test_gil_scoped.cpp;test_thread.cpp" ${{ matrix.args }} - name: Build (unstable ABI) @@ -173,7 +183,9 @@ jobs: # This makes sure the setup_helpers module can build packages using # setuptools - name: Setuptools helpers test - run: pytest tests/extra_setuptools + run: | + pip install setuptools + pytest tests/extra_setuptools if: "!(matrix.runs-on == 'windows-2022')" @@ -186,23 +198,23 @@ jobs: - python-version: "3.9" python-debug: true valgrind: true - - python-version: "3.11-dev" + - python-version: "3.11" python-debug: false name: "🐍 ${{ matrix.python-version }}${{ matrix.python-debug && '-dbg' || '' }} (deadsnakes)${{ matrix.valgrind && ' • Valgrind' || '' }} • x64" - runs-on: ubuntu-latest + runs-on: ubuntu-20.04 steps: - uses: actions/checkout@v3 - name: Setup Python ${{ matrix.python-version }} (deadsnakes) - uses: deadsnakes/action@v2.1.1 + uses: deadsnakes/action@v3.0.1 with: python-version: ${{ matrix.python-version }} debug: ${{ matrix.python-debug }} - name: Update CMake - uses: jwlawson/actions-setup-cmake@v1.12 + uses: jwlawson/actions-setup-cmake@v1.14 - name: Valgrind cache if: matrix.valgrind @@ -235,8 +247,6 @@ jobs: python -m pip install -r tests/requirements.txt - name: Configure - env: - SETUPTOOLS_USE_DISTUTILS: stdlib run: > cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug @@ -274,18 +284,27 @@ jobs: - dev std: - 11 + container_suffix: + - "" include: - clang: 5 std: 14 - - clang: 10 - std: 20 - clang: 10 std: 17 + - clang: 11 + std: 20 + - clang: 12 + std: 20 + - clang: 13 + std: 20 - clang: 14 std: 20 + - clang: 15 + std: 20 + container_suffix: "-bullseye" name: "🐍 3 • Clang ${{ matrix.clang }} • C++${{ matrix.std }} • x64" - container: "silkeh/clang:${{ matrix.clang }}" + container: "silkeh/clang:${{ matrix.clang }}${{ matrix.container_suffix }}" steps: - uses: actions/checkout@v3 @@ -318,8 +337,8 @@ jobs: # Testing NVCC; forces sources to behave like .cu files cuda: runs-on: ubuntu-latest - name: "🐍 3.8 • CUDA 11.2 • Ubuntu 20.04" - container: nvidia/cuda:11.2.2-devel-ubuntu20.04 + name: "🐍 3.10 • CUDA 12.2 • Ubuntu 22.04" + container: nvidia/cuda:12.2.0-devel-ubuntu22.04 steps: - uses: actions/checkout@v3 @@ -384,8 +403,9 @@ jobs: # Testing on CentOS 7 + PGI compilers, which seems to require more workarounds centos-nvhpc7: + if: ${{ false }} # JOB DISABLED (NEEDS WORK): https://github.com/pybind/pybind11/issues/4690 runs-on: ubuntu-latest - name: "🐍 3 • CentOS7 / PGI 22.3 • x64" + name: "🐍 3 • CentOS7 / PGI 22.9 • x64" container: centos:7 steps: @@ -395,7 +415,7 @@ jobs: run: yum update -y && yum install -y epel-release && yum install -y git python3-devel make environment-modules cmake3 yum-utils - name: Install NVidia HPC SDK - run: yum-config-manager --add-repo https://developer.download.nvidia.com/hpc-sdk/rhel/nvhpc.repo && yum -y install nvhpc-22.3 + run: yum-config-manager --add-repo https://developer.download.nvidia.com/hpc-sdk/rhel/nvhpc.repo && yum -y install nvhpc-22.9 # On CentOS 7, we have to filter a few tests (compiler internal error) # and allow deeper template recursion (not needed on CentOS 8 with a newer @@ -405,12 +425,12 @@ jobs: shell: bash run: | source /etc/profile.d/modules.sh - module load /opt/nvidia/hpc_sdk/modulefiles/nvhpc/22.3 + module load /opt/nvidia/hpc_sdk/modulefiles/nvhpc/22.9 cmake3 -S . -B build -DDOWNLOAD_CATCH=ON \ -DCMAKE_CXX_STANDARD=11 \ -DPYTHON_EXECUTABLE=$(python3 -c "import sys; print(sys.executable)") \ -DCMAKE_CXX_FLAGS="-Wc,--pending_instantiations=0" \ - -DPYBIND11_TEST_FILTER="test_smart_ptr.cpp;test_virtual_functions.cpp" + -DPYBIND11_TEST_FILTER="test_smart_ptr.cpp" # Building before installing Pip should produce a warning but not an error - name: Build @@ -437,14 +457,14 @@ jobs: strategy: fail-fast: false matrix: - gcc: - - 7 - - latest - std: - - 11 include: - - gcc: 10 - std: 20 + - { gcc: 7, std: 11 } + - { gcc: 7, std: 17 } + - { gcc: 8, std: 14 } + - { gcc: 8, std: 17 } + - { gcc: 10, std: 17 } + - { gcc: 11, std: 20 } + - { gcc: 12, std: 20 } name: "🐍 3 • GCC ${{ matrix.gcc }} • C++${{ matrix.std }}• x64" container: "gcc:${{ matrix.gcc }}" @@ -459,7 +479,7 @@ jobs: run: python3 -m pip install --upgrade pip - name: Update CMake - uses: jwlawson/actions-setup-cmake@v1.12 + uses: jwlawson/actions-setup-cmake@v1.14 - name: Configure shell: bash @@ -482,6 +502,24 @@ jobs: - name: Interface test run: cmake --build build --target test_cmake_build + - name: Configure - Exercise cmake -DPYBIND11_TEST_OVERRIDE + if: matrix.gcc == '12' + shell: bash + run: > + cmake -S . -B build_partial + -DPYBIND11_WERROR=ON + -DDOWNLOAD_CATCH=ON + -DCMAKE_CXX_STANDARD=${{ matrix.std }} + -DPYTHON_EXECUTABLE=$(python3 -c "import sys; print(sys.executable)") + "-DPYBIND11_TEST_OVERRIDE=test_call_policies.cpp;test_gil_scoped.cpp;test_thread.cpp" + + - name: Build - Exercise cmake -DPYBIND11_TEST_OVERRIDE + if: matrix.gcc == '12' + run: cmake --build build_partial -j 2 + + - name: Python tests - Exercise cmake -DPYBIND11_TEST_OVERRIDE + if: matrix.gcc == '12' + run: cmake --build build_partial --target pytest # Testing on ICC using the oneAPI apt repo icc: @@ -731,6 +769,9 @@ jobs: args: -DCMAKE_CXX_STANDARD=20 - python: 3.8 args: -DCMAKE_CXX_STANDARD=17 + - python: 3.7 + args: -DCMAKE_CXX_STANDARD=14 + name: "🐍 ${{ matrix.python }} • MSVC 2019 • x86 ${{ matrix.args }}" runs-on: windows-2019 @@ -745,10 +786,10 @@ jobs: architecture: x86 - name: Update CMake - uses: jwlawson/actions-setup-cmake@v1.12 + uses: jwlawson/actions-setup-cmake@v1.14 - name: Prepare MSVC - uses: ilammy/msvc-dev-cmd@v1.10.0 + uses: ilammy/msvc-dev-cmd@v1.12.1 with: arch: x86 @@ -798,10 +839,10 @@ jobs: architecture: x86 - name: Update CMake - uses: jwlawson/actions-setup-cmake@v1.12 + uses: jwlawson/actions-setup-cmake@v1.14 - name: Prepare MSVC - uses: ilammy/msvc-dev-cmd@v1.10.0 + uses: ilammy/msvc-dev-cmd@v1.12.1 with: arch: x86 @@ -849,7 +890,7 @@ jobs: python3 -m pip install -r tests/requirements.txt - name: Update CMake - uses: jwlawson/actions-setup-cmake@v1.12 + uses: jwlawson/actions-setup-cmake@v1.14 - name: Configure C++20 run: > @@ -871,6 +912,21 @@ jobs: - name: Interface test C++20 run: cmake --build build --target test_cmake_build + - name: Configure C++20 - Exercise cmake -DPYBIND11_TEST_OVERRIDE + run: > + cmake -S . -B build_partial + -DPYBIND11_WERROR=ON + -DDOWNLOAD_CATCH=ON + -DDOWNLOAD_EIGEN=ON + -DCMAKE_CXX_STANDARD=20 + "-DPYBIND11_TEST_OVERRIDE=test_call_policies.cpp;test_gil_scoped.cpp;test_thread.cpp" + + - name: Build C++20 - Exercise cmake -DPYBIND11_TEST_OVERRIDE + run: cmake --build build_partial -j 2 + + - name: Python tests - Exercise cmake -DPYBIND11_TEST_OVERRIDE + run: cmake --build build_partial --target pytest + mingw: name: "🐍 3 • windows-latest • ${{ matrix.sys }}" runs-on: windows-latest @@ -905,7 +961,7 @@ jobs: - name: Configure C++11 # LTO leads to many undefined reference like # `pybind11::detail::function_call::function_call(pybind11::detail::function_call&&) - run: cmake -G "MinGW Makefiles" -DCMAKE_CXX_STANDARD=11 -DDOWNLOAD_CATCH=ON -S . -B build + run: cmake -G "MinGW Makefiles" -DCMAKE_CXX_STANDARD=11 -DPYBIND11_WERROR=ON -DDOWNLOAD_CATCH=ON -S . -B build - name: Build C++11 run: cmake --build build -j 2 @@ -923,7 +979,7 @@ jobs: run: git clean -fdx - name: Configure C++14 - run: cmake -G "MinGW Makefiles" -DCMAKE_CXX_STANDARD=14 -DDOWNLOAD_CATCH=ON -S . -B build2 + run: cmake -G "MinGW Makefiles" -DCMAKE_CXX_STANDARD=14 -DPYBIND11_WERROR=ON -DDOWNLOAD_CATCH=ON -S . -B build2 - name: Build C++14 run: cmake --build build2 -j 2 @@ -941,7 +997,7 @@ jobs: run: git clean -fdx - name: Configure C++17 - run: cmake -G "MinGW Makefiles" -DCMAKE_CXX_STANDARD=17 -DDOWNLOAD_CATCH=ON -S . -B build3 + run: cmake -G "MinGW Makefiles" -DCMAKE_CXX_STANDARD=17 -DPYBIND11_WERROR=ON -DDOWNLOAD_CATCH=ON -S . -B build3 - name: Build C++17 run: cmake --build build3 -j 2 @@ -954,3 +1010,156 @@ jobs: - name: Interface test C++17 run: PYTHONHOME=/${{matrix.sys}} PYTHONPATH=/${{matrix.sys}} cmake --build build3 --target test_cmake_build + + windows_clang: + + strategy: + matrix: + os: [windows-latest] + python: ['3.10'] + + runs-on: "${{ matrix.os }}" + + name: "🐍 ${{ matrix.python }} • ${{ matrix.os }} • clang-latest" + + steps: + - name: Show env + run: env + + - name: Checkout + uses: actions/checkout@v3 + + - name: Set up Clang + uses: egor-tensin/setup-clang@v1 + + - name: Setup Python ${{ matrix.python }} + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python }} + + - name: Update CMake + uses: jwlawson/actions-setup-cmake@v1.14 + + - name: Install ninja-build tool + uses: seanmiddleditch/gha-setup-ninja@v3 + + - name: Run pip installs + run: | + python -m pip install --upgrade pip + python -m pip install -r tests/requirements.txt + + - name: Show Clang++ version + run: clang++ --version + + - name: Show CMake version + run: cmake --version + + # TODO: WERROR=ON + - name: Configure Clang + run: > + cmake -G Ninja -S . -B . + -DPYBIND11_WERROR=OFF + -DPYBIND11_SIMPLE_GIL_MANAGEMENT=OFF + -DDOWNLOAD_CATCH=ON + -DDOWNLOAD_EIGEN=ON + -DCMAKE_CXX_COMPILER=clang++ + -DCMAKE_CXX_STANDARD=17 + + - name: Build + run: cmake --build . -j 2 + + - name: Python tests + run: cmake --build . --target pytest -j 2 + + - name: C++ tests + run: cmake --build . --target cpptest -j 2 + + - name: Interface test + run: cmake --build . --target test_cmake_build -j 2 + + - name: Clean directory + run: git clean -fdx + + macos_brew_install_llvm: + name: "macos-latest • brew install llvm" + runs-on: macos-latest + + env: + # https://apple.stackexchange.com/questions/227026/how-to-install-recent-clang-with-homebrew + LDFLAGS: '-L/usr/local/opt/llvm/lib -Wl,-rpath,/usr/local/opt/llvm/lib' + + steps: + - name: Update PATH + run: echo "/usr/local/opt/llvm/bin" >> $GITHUB_PATH + + - name: Show env + run: env + + - name: Checkout + uses: actions/checkout@v3 + + - name: Show Clang++ version before brew install llvm + run: clang++ --version + + - name: brew install llvm + run: brew install llvm + + - name: Show Clang++ version after brew install llvm + run: clang++ --version + + - name: Update CMake + uses: jwlawson/actions-setup-cmake@v1.14 + + - name: Run pip installs + run: | + python3 -m pip install --upgrade pip + python3 -m pip install -r tests/requirements.txt + python3 -m pip install numpy + python3 -m pip install scipy + + - name: Show CMake version + run: cmake --version + + - name: CMake Configure + run: > + cmake -S . -B . + -DPYBIND11_WERROR=ON + -DPYBIND11_SIMPLE_GIL_MANAGEMENT=OFF + -DDOWNLOAD_CATCH=ON + -DDOWNLOAD_EIGEN=ON + -DCMAKE_CXX_COMPILER=clang++ + -DCMAKE_CXX_STANDARD=17 + -DPYTHON_EXECUTABLE=$(python3 -c "import sys; print(sys.executable)") + + - name: Build + run: cmake --build . -j 2 + + - name: Python tests + run: cmake --build . --target pytest -j 2 + + - name: C++ tests + run: cmake --build . --target cpptest -j 2 + + - name: Interface test + run: cmake --build . --target test_cmake_build -j 2 + + - name: CMake Configure - Exercise cmake -DPYBIND11_TEST_OVERRIDE + run: > + cmake -S . -B build_partial + -DPYBIND11_WERROR=ON + -DPYBIND11_SIMPLE_GIL_MANAGEMENT=OFF + -DDOWNLOAD_CATCH=ON + -DDOWNLOAD_EIGEN=ON + -DCMAKE_CXX_COMPILER=clang++ + -DCMAKE_CXX_STANDARD=17 + -DPYTHON_EXECUTABLE=$(python3 -c "import sys; print(sys.executable)") + "-DPYBIND11_TEST_OVERRIDE=test_call_policies.cpp;test_gil_scoped.cpp;test_thread.cpp" + + - name: Build - Exercise cmake -DPYBIND11_TEST_OVERRIDE + run: cmake --build build_partial -j 2 + + - name: Python tests - Exercise cmake -DPYBIND11_TEST_OVERRIDE + run: cmake --build build_partial --target pytest -j 2 + + - name: Clean directory + run: git clean -fdx diff --git a/pybind11/.github/workflows/configure.yml b/pybind11/.github/workflows/configure.yml index edcad4198..ec7cd612d 100644 --- a/pybind11/.github/workflows/configure.yml +++ b/pybind11/.github/workflows/configure.yml @@ -9,6 +9,14 @@ on: - stable - v* +permissions: + contents: read + +env: + PIP_BREAK_SYSTEM_PACKAGES: 1 + # For cmake: + VERBOSE: 1 + jobs: # This tests various versions of CMake in various combinations, to make sure # the configure step passes. @@ -16,22 +24,26 @@ jobs: strategy: fail-fast: false matrix: - runs-on: [ubuntu-latest, macos-latest, windows-latest] + runs-on: [ubuntu-20.04, macos-latest, windows-latest] arch: [x64] - cmake: ["3.23"] + cmake: ["3.26"] include: - - runs-on: ubuntu-latest + - runs-on: ubuntu-20.04 arch: x64 - cmake: 3.4 + cmake: "3.5" + + - runs-on: ubuntu-20.04 + arch: x64 + cmake: "3.27" - runs-on: macos-latest arch: x64 - cmake: 3.7 + cmake: "3.7" - runs-on: windows-2019 arch: x64 # x86 compilers seem to be missing on 2019 image - cmake: 3.18 + cmake: "3.18" name: 🐍 3.7 • CMake ${{ matrix.cmake }} • ${{ matrix.runs-on }} runs-on: ${{ matrix.runs-on }} @@ -51,7 +63,7 @@ jobs: # An action for adding a specific version of CMake: # https://github.com/jwlawson/actions-setup-cmake - name: Setup CMake ${{ matrix.cmake }} - uses: jwlawson/actions-setup-cmake@v1.12 + uses: jwlawson/actions-setup-cmake@v1.14 with: cmake-version: ${{ matrix.cmake }} diff --git a/pybind11/.github/workflows/format.yml b/pybind11/.github/workflows/format.yml index 31d893c47..b8242ee52 100644 --- a/pybind11/.github/workflows/format.yml +++ b/pybind11/.github/workflows/format.yml @@ -12,8 +12,13 @@ on: - stable - "v*" +permissions: + contents: read + env: FORCE_COLOR: 3 + # For cmake: + VERBOSE: 1 jobs: pre-commit: @@ -36,12 +41,12 @@ jobs: # in .github/CONTRIBUTING.md and update as needed. name: Clang-Tidy runs-on: ubuntu-latest - container: silkeh/clang:13 + container: silkeh/clang:15-bullseye steps: - uses: actions/checkout@v3 - name: Install requirements - run: apt-get update && apt-get install -y python3-dev python3-pytest + run: apt-get update && apt-get install -y git python3-dev python3-pytest - name: Configure run: > diff --git a/pybind11/.github/workflows/labeler.yml b/pybind11/.github/workflows/labeler.yml index d2b597968..858a4a0e2 100644 --- a/pybind11/.github/workflows/labeler.yml +++ b/pybind11/.github/workflows/labeler.yml @@ -3,14 +3,23 @@ on: pull_request_target: types: [closed] +permissions: {} + jobs: label: name: Labeler runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: write steps: - uses: actions/labeler@main - if: github.event.pull_request.merged == true + if: > + github.event.pull_request.merged == true && + !startsWith(github.event.pull_request.title, 'chore(deps):') && + !startsWith(github.event.pull_request.title, 'ci(fix):') && + !startsWith(github.event.pull_request.title, 'docs(changelog):') with: repo-token: ${{ secrets.GITHUB_TOKEN }} configuration-path: .github/labeler_merged.yml diff --git a/pybind11/.github/workflows/pip.yml b/pybind11/.github/workflows/pip.yml index 2c16735e1..d6687b441 100644 --- a/pybind11/.github/workflows/pip.yml +++ b/pybind11/.github/workflows/pip.yml @@ -12,7 +12,11 @@ on: types: - published +permissions: + contents: read + env: + PIP_BREAK_SYSTEM_PACKAGES: 1 PIP_ONLY_BINARY: numpy jobs: @@ -98,13 +102,13 @@ jobs: - uses: actions/download-artifact@v3 - name: Publish standard package - uses: pypa/gh-action-pypi-publish@v1.5.0 + uses: pypa/gh-action-pypi-publish@release/v1 with: password: ${{ secrets.pypi_password }} - packages_dir: standard/ + packages-dir: standard/ - name: Publish global package - uses: pypa/gh-action-pypi-publish@v1.5.0 + uses: pypa/gh-action-pypi-publish@release/v1 with: password: ${{ secrets.pypi_password_global }} - packages_dir: global/ + packages-dir: global/ diff --git a/pybind11/.github/workflows/upstream.yml b/pybind11/.github/workflows/upstream.yml index a40a6c7d7..dd8a1c960 100644 --- a/pybind11/.github/workflows/upstream.yml +++ b/pybind11/.github/workflows/upstream.yml @@ -1,112 +1,116 @@ - name: Upstream on: workflow_dispatch: pull_request: +permissions: + contents: read + concurrency: group: upstream-${{ github.ref }} cancel-in-progress: true env: - PIP_ONLY_BINARY: numpy + PIP_BREAK_SYSTEM_PACKAGES: 1 + PIP_ONLY_BINARY: ":all:" + # For cmake: + VERBOSE: 1 jobs: standard: - name: "🐍 3.11 latest internals • ubuntu-latest • x64" + name: "🐍 3.12 latest • ubuntu-latest • x64" runs-on: ubuntu-latest + # Only runs when the 'python dev' label is selected if: "contains(github.event.pull_request.labels.*.name, 'python dev')" steps: - uses: actions/checkout@v3 - - name: Setup Python 3.11 + - name: Setup Python 3.12 uses: actions/setup-python@v4 with: - python-version: "3.11-dev" + python-version: "3.12-dev" - - name: Setup Boost (Linux) - if: runner.os == 'Linux' + - name: Setup Boost run: sudo apt-get install libboost-dev - name: Update CMake - uses: jwlawson/actions-setup-cmake@v1.12 + uses: jwlawson/actions-setup-cmake@v1.14 - - name: Prepare env + - name: Run pip installs run: | + python -m pip install --upgrade pip python -m pip install -r tests/requirements.txt - - name: Setup annotations on Linux - if: runner.os == 'Linux' - run: python -m pip install pytest-github-actions-annotate-failures + - name: Show platform info + run: | + python -m platform + cmake --version + pip list # First build - C++11 mode and inplace - name: Configure C++11 run: > - cmake -S . -B . + cmake -S . -B build11 -DPYBIND11_WERROR=ON -DDOWNLOAD_CATCH=ON -DDOWNLOAD_EIGEN=ON -DCMAKE_CXX_STANDARD=11 + -DCMAKE_BUILD_TYPE=Debug - name: Build C++11 - run: cmake --build . -j 2 + run: cmake --build build11 -j 2 - name: Python tests C++11 - run: cmake --build . --target pytest -j 2 + run: cmake --build build11 --target pytest -j 2 - name: C++11 tests - run: cmake --build . --target cpptest -j 2 + run: cmake --build build11 --target cpptest -j 2 - name: Interface test C++11 - run: cmake --build . --target test_cmake_build - - - name: Clean directory - run: git clean -fdx + run: cmake --build build11 --target test_cmake_build # Second build - C++17 mode and in a build directory - name: Configure C++17 run: > - cmake -S . -B build2 + cmake -S . -B build17 -DPYBIND11_WERROR=ON -DDOWNLOAD_CATCH=ON -DDOWNLOAD_EIGEN=ON -DCMAKE_CXX_STANDARD=17 - ${{ matrix.args }} - ${{ matrix.args2 }} - - name: Build - run: cmake --build build2 -j 2 + - name: Build C++17 + run: cmake --build build17 -j 2 - - name: Python tests - run: cmake --build build2 --target pytest + - name: Python tests C++17 + run: cmake --build build17 --target pytest - - name: C++ tests - run: cmake --build build2 --target cpptest + - name: C++17 tests + run: cmake --build build17 --target cpptest # Third build - C++17 mode with unstable ABI - name: Configure (unstable ABI) run: > - cmake -S . -B build3 + cmake -S . -B build17max -DPYBIND11_WERROR=ON -DDOWNLOAD_CATCH=ON -DDOWNLOAD_EIGEN=ON -DCMAKE_CXX_STANDARD=17 -DPYBIND11_INTERNALS_VERSION=10000000 - "-DPYBIND11_TEST_OVERRIDE=test_call_policies.cpp;test_gil_scoped.cpp;test_thread.cpp" - ${{ matrix.args }} - name: Build (unstable ABI) - run: cmake --build build3 -j 2 + run: cmake --build build17max -j 2 - name: Python tests (unstable ABI) - run: cmake --build build3 --target pytest + run: cmake --build build17max --target pytest - - name: Interface test - run: cmake --build build2 --target test_cmake_build + - name: Interface test (unstable ABI) + run: cmake --build build17max --target test_cmake_build # This makes sure the setup_helpers module can build packages using # setuptools - name: Setuptools helpers test - run: pytest tests/extra_setuptools + run: | + pip install setuptools + pytest tests/extra_setuptools diff --git a/pybind11/.gitignore b/pybind11/.gitignore index 3cf4fbbda..43d5094c9 100644 --- a/pybind11/.gitignore +++ b/pybind11/.gitignore @@ -43,3 +43,4 @@ pybind11Targets.cmake /pybind11/share/* /docs/_build/* .ipynb_checkpoints/ +tests/main.cpp diff --git a/pybind11/.pre-commit-config.yaml b/pybind11/.pre-commit-config.yaml index ba9955a31..86ac965d9 100644 --- a/pybind11/.pre-commit-config.yaml +++ b/pybind11/.pre-commit-config.yaml @@ -12,10 +12,62 @@ # # See https://github.com/pre-commit/pre-commit + +ci: + autoupdate_commit_msg: "chore(deps): update pre-commit hooks" + autofix_commit_msg: "style: pre-commit fixes" + autoupdate_schedule: monthly + +# third-party content +exclude: ^tools/JoinPaths.cmake$ + repos: + +# Clang format the codebase automatically +- repo: https://github.com/pre-commit/mirrors-clang-format + rev: "v16.0.6" + hooks: + - id: clang-format + types_or: [c++, c, cuda] + +# Black, the code formatter, natively supports pre-commit +- repo: https://github.com/psf/black + rev: "23.3.0" # Keep in sync with blacken-docs + hooks: + - id: black + +# Ruff, the Python auto-correcting linter written in Rust +- repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.0.276 + hooks: + - id: ruff + args: ["--fix", "--show-fixes"] + +# Check static types with mypy +- repo: https://github.com/pre-commit/mirrors-mypy + rev: "v1.4.1" + hooks: + - id: mypy + args: [] + exclude: ^(tests|docs)/ + additional_dependencies: + - markdown-it-py<3 # Drop this together with dropping Python 3.7 support. + - nox + - rich + - types-setuptools + +# CMake formatting +- repo: https://github.com/cheshirekow/cmake-format-precommit + rev: "v0.6.13" + hooks: + - id: cmake-format + additional_dependencies: [pyyaml] + types: [file] + files: (\.cmake|CMakeLists.txt)(.in)?$ + # Standard hooks - repo: https://github.com/pre-commit/pre-commit-hooks - rev: "v4.3.0" + rev: "v4.4.0" hooks: - id: check-added-large-files - id: check-case-conflict @@ -30,109 +82,38 @@ repos: - id: requirements-txt-fixer - id: trailing-whitespace -# Upgrade old Python syntax -- repo: https://github.com/asottile/pyupgrade - rev: "v2.37.1" - hooks: - - id: pyupgrade - args: [--py36-plus] - -# Nicely sort includes -- repo: https://github.com/PyCQA/isort - rev: "5.10.1" - hooks: - - id: isort - -# Black, the code formatter, natively supports pre-commit -- repo: https://github.com/psf/black - rev: "22.6.0" # Keep in sync with blacken-docs - hooks: - - id: black - # Also code format the docs - repo: https://github.com/asottile/blacken-docs - rev: "v1.12.1" + rev: "1.14.0" hooks: - id: blacken-docs additional_dependencies: - - black==22.6.0 # keep in sync with black hook + - black==23.3.0 # keep in sync with black hook # Changes tabs to spaces - repo: https://github.com/Lucas-C/pre-commit-hooks - rev: "v1.3.0" + rev: "v1.5.1" hooks: - id: remove-tabs +# Avoid directional quotes - repo: https://github.com/sirosen/texthooks - rev: "0.3.1" + rev: "0.5.0" hooks: - id: fix-ligatures - id: fix-smartquotes -# Autoremoves unused imports -- repo: https://github.com/hadialqattan/pycln - rev: "v2.0.1" - hooks: - - id: pycln - stages: [manual] - # Checking for common mistakes - repo: https://github.com/pre-commit/pygrep-hooks - rev: "v1.9.0" + rev: "v1.10.0" hooks: - - id: python-check-blanket-noqa - - id: python-check-blanket-type-ignore - - id: python-no-log-warn - - id: python-use-type-annotations - id: rst-backticks - id: rst-directive-colons - id: rst-inline-touching-normal -# Automatically remove noqa that are not used -- repo: https://github.com/asottile/yesqa - rev: "v1.3.0" - hooks: - - id: yesqa - additional_dependencies: &flake8_dependencies - - flake8-bugbear - - pep8-naming - -# Flake8 also supports pre-commit natively (same author) -- repo: https://github.com/PyCQA/flake8 - rev: "4.0.1" - hooks: - - id: flake8 - exclude: ^(docs/.*|tools/.*)$ - additional_dependencies: *flake8_dependencies - -# PyLint has native support - not always usable, but works for us -- repo: https://github.com/PyCQA/pylint - rev: "v2.14.4" - hooks: - - id: pylint - files: ^pybind11 - -# CMake formatting -- repo: https://github.com/cheshirekow/cmake-format-precommit - rev: "v0.6.13" - hooks: - - id: cmake-format - additional_dependencies: [pyyaml] - types: [file] - files: (\.cmake|CMakeLists.txt)(.in)?$ - -# Check static types with mypy -- repo: https://github.com/pre-commit/mirrors-mypy - rev: "v0.961" - hooks: - - id: mypy - args: [] - exclude: ^(tests|docs)/ - additional_dependencies: [nox, rich] - # Checks the manifest for missing files (native support) - repo: https://github.com/mgedmin/check-manifest - rev: "0.48" + rev: "0.49" hooks: - id: check-manifest # This is a slow hook, so only run this if --hook-stage manual is passed @@ -140,16 +121,18 @@ repos: additional_dependencies: [cmake, ninja] # Check for spelling +# Use tools/codespell_ignore_lines_from_errors.py +# to rebuild .codespell-ignore-lines - repo: https://github.com/codespell-project/codespell - rev: "v2.1.0" + rev: "v2.2.5" hooks: - id: codespell exclude: ".supp$" - args: ["-L", "nd,ot,thist"] + args: ["-x.codespell-ignore-lines", "-Lccompiler"] # Check for common shell mistakes - repo: https://github.com/shellcheck-py/shellcheck-py - rev: "v0.8.0.4" + rev: "v0.9.0.5" hooks: - id: shellcheck @@ -162,9 +145,9 @@ repos: entry: PyBind|Numpy|Cmake|CCache|PyTest exclude: ^\.pre-commit-config.yaml$ -# Clang format the codebase automatically -- repo: https://github.com/pre-commit/mirrors-clang-format - rev: "v14.0.6" +# PyLint has native support - not always usable, but works for us +- repo: https://github.com/PyCQA/pylint + rev: "v3.0.0a6" hooks: - - id: clang-format - types_or: [c++, c, cuda] + - id: pylint + files: ^pybind11 diff --git a/pybind11/CMakeLists.txt b/pybind11/CMakeLists.txt index 3787982cb..87ec10346 100644 --- a/pybind11/CMakeLists.txt +++ b/pybind11/CMakeLists.txt @@ -5,15 +5,15 @@ # All rights reserved. Use of this source code is governed by a # BSD-style license that can be found in the LICENSE file. -cmake_minimum_required(VERSION 3.4) +cmake_minimum_required(VERSION 3.5) -# The `cmake_minimum_required(VERSION 3.4...3.22)` syntax does not work with +# The `cmake_minimum_required(VERSION 3.5...3.26)` syntax does not work with # some versions of VS that have a patched CMake 3.11. This forces us to emulate # the behavior using the following workaround: -if(${CMAKE_VERSION} VERSION_LESS 3.22) +if(${CMAKE_VERSION} VERSION_LESS 3.26) cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}) else() - cmake_policy(VERSION 3.22) + cmake_policy(VERSION 3.26) endif() # Avoid infinite recursion if tests include this as a subdirectory @@ -91,10 +91,16 @@ endif() option(PYBIND11_INSTALL "Install pybind11 header files?" ${PYBIND11_MASTER_PROJECT}) option(PYBIND11_TEST "Build pybind11 test suite?" ${PYBIND11_MASTER_PROJECT}) option(PYBIND11_NOPYTHON "Disable search for Python" OFF) +option(PYBIND11_SIMPLE_GIL_MANAGEMENT + "Use simpler GIL management logic that does not support disassociation" OFF) set(PYBIND11_INTERNALS_VERSION "" CACHE STRING "Override the ABI version, may be used to enable the unstable ABI.") +if(PYBIND11_SIMPLE_GIL_MANAGEMENT) + add_compile_definitions(PYBIND11_SIMPLE_GIL_MANAGEMENT) +endif() + cmake_dependent_option( USE_PYTHON_INCLUDE_DIR "Install pybind11 headers in Python include directory instead of default installation prefix" @@ -120,6 +126,9 @@ set(PYBIND11_HEADERS include/pybind11/complex.h include/pybind11/options.h include/pybind11/eigen.h + include/pybind11/eigen/common.h + include/pybind11/eigen/matrix.h + include/pybind11/eigen/tensor.h include/pybind11/embed.h include/pybind11/eval.h include/pybind11/gil.h @@ -131,7 +140,8 @@ set(PYBIND11_HEADERS include/pybind11/pytypes.h include/pybind11/stl.h include/pybind11/stl_bind.h - include/pybind11/stl/filesystem.h) + include/pybind11/stl/filesystem.h + include/pybind11/type_caster_pyobject_ptr.h) # Compare with grep and warn if mismatched if(PYBIND11_MASTER_PROJECT AND NOT CMAKE_VERSION VERSION_LESS 3.12) @@ -198,6 +208,9 @@ else() endif() include("${CMAKE_CURRENT_SOURCE_DIR}/tools/pybind11Common.cmake") +# https://github.com/jtojnar/cmake-snips/#concatenating-paths-when-building-pkg-config-files +# TODO: cmake 3.20 adds the cmake_path() function, which obsoletes this snippet +include("${CMAKE_CURRENT_SOURCE_DIR}/tools/JoinPaths.cmake") # Relative directory setting if(USE_PYTHON_INCLUDE_DIR AND DEFINED Python_INCLUDE_DIRS) @@ -262,6 +275,16 @@ if(PYBIND11_INSTALL) NAMESPACE "pybind11::" DESTINATION ${PYBIND11_CMAKECONFIG_INSTALL_DIR}) + # pkg-config support + if(NOT prefix_for_pc_file) + set(prefix_for_pc_file "${CMAKE_INSTALL_PREFIX}") + endif() + join_paths(includedir_for_pc_file "\${prefix}" "${CMAKE_INSTALL_INCLUDEDIR}") + configure_file("${CMAKE_CURRENT_SOURCE_DIR}/tools/pybind11.pc.in" + "${CMAKE_CURRENT_BINARY_DIR}/pybind11.pc" @ONLY) + install(FILES "${CMAKE_CURRENT_BINARY_DIR}/pybind11.pc" + DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/pkgconfig/") + # Uninstall target if(PYBIND11_MASTER_PROJECT) configure_file("${CMAKE_CURRENT_SOURCE_DIR}/tools/cmake_uninstall.cmake.in" diff --git a/pybind11/MANIFEST.in b/pybind11/MANIFEST.in index 033303a74..7ce83c552 100644 --- a/pybind11/MANIFEST.in +++ b/pybind11/MANIFEST.in @@ -1,5 +1,6 @@ +prune tests recursive-include pybind11/include/pybind11 *.h recursive-include pybind11 *.py recursive-include pybind11 py.typed include pybind11/share/cmake/pybind11/*.cmake -include LICENSE README.rst pyproject.toml setup.py setup.cfg +include LICENSE README.rst SECURITY.md pyproject.toml setup.py setup.cfg diff --git a/pybind11/README.rst b/pybind11/README.rst index 3c75edb57..80213a406 100644 --- a/pybind11/README.rst +++ b/pybind11/README.rst @@ -135,7 +135,7 @@ This project was created by `Wenzel Jakob `_. Significant features and/or improvements to the code were contributed by Jonas Adler, Lori A. Burns, Sylvain Corlay, Eric Cousineau, Aaron Gokaslan, Ralf Grosse-Kunstleve, Trent Houliston, Axel -Huebl, @hulucc, Yannick Jadoul, Sergey Lyskov Johan Mabille, Tomasz Miąsko, +Huebl, @hulucc, Yannick Jadoul, Sergey Lyskov, Johan Mabille, Tomasz Miąsko, Dean Moldovan, Ben Pritchard, Jason Rhinelander, Boris Schäling, Pim Schellart, Henry Schreiner, Ivan Smirnov, Boris Staletic, and Patrick Stewart. diff --git a/pybind11/SECURITY.md b/pybind11/SECURITY.md new file mode 100644 index 000000000..3d74611f2 --- /dev/null +++ b/pybind11/SECURITY.md @@ -0,0 +1,13 @@ +# Security Policy + +## Supported Versions + +Security updates are applied only to the latest release. + +## Reporting a Vulnerability + +If you have discovered a security vulnerability in this project, please report it privately. **Do not disclose it as a public issue.** This gives us time to work with you to fix the issue before public exposure, reducing the chance that the exploit will be used before a patch is released. + +Please disclose it at [security advisory](https://github.com/pybind/pybind11/security/advisories/new). + +This project is maintained by a team of volunteers on a reasonable-effort basis. As such, please give us at least 90 days to work on a fix before public exposure. diff --git a/pybind11/docs/advanced/cast/custom.rst b/pybind11/docs/advanced/cast/custom.rst index 1df4d3e14..8138cac61 100644 --- a/pybind11/docs/advanced/cast/custom.rst +++ b/pybind11/docs/advanced/cast/custom.rst @@ -38,7 +38,7 @@ type is explicitly allowed. .. code-block:: cpp - namespace pybind11 { namespace detail { + namespace PYBIND11_NAMESPACE { namespace detail { template <> struct type_caster { public: /** @@ -78,7 +78,7 @@ type is explicitly allowed. return PyLong_FromLong(src.long_value); } }; - }} // namespace pybind11::detail + }} // namespace PYBIND11_NAMESPACE::detail .. note:: diff --git a/pybind11/docs/advanced/cast/stl.rst b/pybind11/docs/advanced/cast/stl.rst index 109763f7a..03d49b295 100644 --- a/pybind11/docs/advanced/cast/stl.rst +++ b/pybind11/docs/advanced/cast/stl.rst @@ -42,7 +42,7 @@ types: .. code-block:: cpp // `boost::optional` as an example -- can be any `std::optional`-like container - namespace pybind11 { namespace detail { + namespace PYBIND11_NAMESPACE { namespace detail { template struct type_caster> : optional_caster> {}; }} @@ -54,7 +54,7 @@ for custom variant types: .. code-block:: cpp // `boost::variant` as an example -- can be any `std::variant`-like container - namespace pybind11 { namespace detail { + namespace PYBIND11_NAMESPACE { namespace detail { template struct type_caster> : variant_caster> {}; @@ -66,7 +66,7 @@ for custom variant types: return boost::apply_visitor(args...); } }; - }} // namespace pybind11::detail + }} // namespace PYBIND11_NAMESPACE::detail The ``visit_helper`` specialization is not required if your ``name::variant`` provides a ``name::visit()`` function. For any other function name, the specialization must be diff --git a/pybind11/docs/advanced/cast/strings.rst b/pybind11/docs/advanced/cast/strings.rst index e246c5219..271716b4b 100644 --- a/pybind11/docs/advanced/cast/strings.rst +++ b/pybind11/docs/advanced/cast/strings.rst @@ -101,8 +101,11 @@ conversion has the same overhead as implicit conversion. m.def("str_output", []() { std::string s = "Send your r\xe9sum\xe9 to Alice in HR"; // Latin-1 - py::str py_s = PyUnicode_DecodeLatin1(s.data(), s.length()); - return py_s; + py::handle py_s = PyUnicode_DecodeLatin1(s.data(), s.length(), nullptr); + if (!py_s) { + throw py::error_already_set(); + } + return py::reinterpret_steal(py_s); } ); @@ -113,7 +116,8 @@ conversion has the same overhead as implicit conversion. The `Python C API `_ provides -several built-in codecs. +several built-in codecs. Note that these all return *new* references, so +use :cpp:func:`reinterpret_steal` when converting them to a :cpp:class:`str`. One could also use a third party encoding library such as libiconv to transcode diff --git a/pybind11/docs/advanced/classes.rst b/pybind11/docs/advanced/classes.rst index 49ddf5c0b..01a490b72 100644 --- a/pybind11/docs/advanced/classes.rst +++ b/pybind11/docs/advanced/classes.rst @@ -1228,7 +1228,7 @@ whether a downcast is safe, you can proceed by specializing the std::string bark() const { return sound; } }; - namespace pybind11 { + namespace PYBIND11_NAMESPACE { template<> struct polymorphic_type_hook { static const void *get(const Pet *src, const std::type_info*& type) { // note that src may be nullptr @@ -1239,7 +1239,7 @@ whether a downcast is safe, you can proceed by specializing the return src; } }; - } // namespace pybind11 + } // namespace PYBIND11_NAMESPACE When pybind11 wants to convert a C++ pointer of type ``Base*`` to a Python object, it calls ``polymorphic_type_hook::get()`` to diff --git a/pybind11/docs/advanced/embedding.rst b/pybind11/docs/advanced/embedding.rst index dd980d483..e6a1686f8 100644 --- a/pybind11/docs/advanced/embedding.rst +++ b/pybind11/docs/advanced/embedding.rst @@ -18,7 +18,7 @@ information, see :doc:`/compiling`. .. code-block:: cmake - cmake_minimum_required(VERSION 3.4) + cmake_minimum_required(VERSION 3.5...3.26) project(example) find_package(pybind11 REQUIRED) # or `add_subdirectory(pybind11)` diff --git a/pybind11/docs/advanced/exceptions.rst b/pybind11/docs/advanced/exceptions.rst index 2211caf5d..53981dc08 100644 --- a/pybind11/docs/advanced/exceptions.rst +++ b/pybind11/docs/advanced/exceptions.rst @@ -177,9 +177,12 @@ section. may be explicitly (re-)thrown to delegate it to the other, previously-declared existing exception translators. - Note that ``libc++`` and ``libstdc++`` `behave differently `_ - with ``-fvisibility=hidden``. Therefore exceptions that are used across ABI boundaries need to be explicitly exported, as exercised in ``tests/test_exceptions.h``. - See also: "Problems with C++ exceptions" under `GCC Wiki `_. + Note that ``libc++`` and ``libstdc++`` `behave differently under macOS + `_ + with ``-fvisibility=hidden``. Therefore exceptions that are used across ABI + boundaries need to be explicitly exported, as exercised in + ``tests/test_exceptions.h``. See also: + "Problems with C++ exceptions" under `GCC Wiki `_. Local vs Global Exception Translators diff --git a/pybind11/docs/advanced/misc.rst b/pybind11/docs/advanced/misc.rst index edab15fcb..805ec838f 100644 --- a/pybind11/docs/advanced/misc.rst +++ b/pybind11/docs/advanced/misc.rst @@ -39,15 +39,42 @@ The ``PYBIND11_MAKE_OPAQUE`` macro does *not* require the above workarounds. Global Interpreter Lock (GIL) ============================= -When calling a C++ function from Python, the GIL is always held. +The Python C API dictates that the Global Interpreter Lock (GIL) must always +be held by the current thread to safely access Python objects. As a result, +when Python calls into C++ via pybind11 the GIL must be held, and pybind11 +will never implicitly release the GIL. + +.. code-block:: cpp + + void my_function() { + /* GIL is held when this function is called from Python */ + } + + PYBIND11_MODULE(example, m) { + m.def("my_function", &my_function); + } + +pybind11 will ensure that the GIL is held when it knows that it is calling +Python code. For example, if a Python callback is passed to C++ code via +``std::function``, when C++ code calls the function the built-in wrapper +will acquire the GIL before calling the Python callback. Similarly, the +``PYBIND11_OVERRIDE`` family of macros will acquire the GIL before calling +back into Python. + +When writing C++ code that is called from other C++ code, if that code accesses +Python state, it must explicitly acquire and release the GIL. + The classes :class:`gil_scoped_release` and :class:`gil_scoped_acquire` can be used to acquire and release the global interpreter lock in the body of a C++ function call. In this way, long-running C++ code can be parallelized using -multiple Python threads. Taking :ref:`overriding_virtuals` as an example, this +multiple Python threads, **but great care must be taken** when any +:class:`gil_scoped_release` appear: if there is any way that the C++ code +can access Python objects, :class:`gil_scoped_acquire` should be used to +reacquire the GIL. Taking :ref:`overriding_virtuals` as an example, this could be realized as follows (important changes highlighted): .. code-block:: cpp - :emphasize-lines: 8,9,31,32 + :emphasize-lines: 8,30,31 class PyAnimal : public Animal { public: @@ -56,9 +83,7 @@ could be realized as follows (important changes highlighted): /* Trampoline (need one for each virtual function) */ std::string go(int n_times) { - /* Acquire GIL before calling Python code */ - py::gil_scoped_acquire acquire; - + /* PYBIND11_OVERRIDE_PURE will acquire the GIL before accessing Python state */ PYBIND11_OVERRIDE_PURE( std::string, /* Return type */ Animal, /* Parent class */ @@ -78,7 +103,8 @@ could be realized as follows (important changes highlighted): .def(py::init<>()); m.def("call_go", [](Animal *animal) -> std::string { - /* Release GIL before calling into (potentially long-running) C++ code */ + // GIL is held when called from Python code. Release GIL before + // calling into (potentially long-running) C++ code py::gil_scoped_release release; return call_go(animal); }); @@ -92,6 +118,34 @@ The ``call_go`` wrapper can also be simplified using the ``call_guard`` policy m.def("call_go", &call_go, py::call_guard()); +Common Sources Of Global Interpreter Lock Errors +================================================================== + +Failing to properly hold the Global Interpreter Lock (GIL) is one of the +more common sources of bugs within code that uses pybind11. If you are +running into GIL related errors, we highly recommend you consult the +following checklist. + +- Do you have any global variables that are pybind11 objects or invoke + pybind11 functions in either their constructor or destructor? You are generally + not allowed to invoke any Python function in a global static context. We recommend + using lazy initialization and then intentionally leaking at the end of the program. + +- Do you have any pybind11 objects that are members of other C++ structures? One + commonly overlooked requirement is that pybind11 objects have to increase their reference count + whenever their copy constructor is called. Thus, you need to be holding the GIL to invoke + the copy constructor of any C++ class that has a pybind11 member. This can sometimes be very + tricky to track for complicated programs Think carefully when you make a pybind11 object + a member in another struct. + +- C++ destructors that invoke Python functions can be particularly troublesome as + destructors can sometimes get invoked in weird and unexpected circumstances as a result + of exceptions. + +- You should try running your code in a debug build. That will enable additional assertions + within pybind11 that will throw exceptions on certain GIL handling errors + (reference counting operations). + Binding sequence data types, iterators, the slicing protocol, etc. ================================================================== @@ -298,6 +352,15 @@ The class ``options`` allows you to selectively suppress auto-generated signatur m.def("add", [](int a, int b) { return a + b; }, "A function which adds two numbers"); } +pybind11 also appends all members of an enum to the resulting enum docstring. +This default behavior can be disabled by using the ``disable_enum_members_docstring()`` +function of the ``options`` class. + +With ``disable_user_defined_docstrings()`` all user defined docstrings of +``module_::def()``, ``class_::def()`` and ``enum_()`` are disabled, but the +function signatures and enum members are included in the docstring, unless they +are disabled separately. + Note that changes to the settings affect only function bindings created during the lifetime of the ``options`` instance. When it goes out of scope at the end of the module's init function, the default settings are restored to prevent unwanted side effects. diff --git a/pybind11/docs/advanced/pycpp/numpy.rst b/pybind11/docs/advanced/pycpp/numpy.rst index b6ef019ed..07c969305 100644 --- a/pybind11/docs/advanced/pycpp/numpy.rst +++ b/pybind11/docs/advanced/pycpp/numpy.rst @@ -433,7 +433,7 @@ following: { 2, 4 }, // shape (rows, cols) { sizeof(uint8_t) * 4, sizeof(uint8_t) } // strides in bytes ); - }) + }); This approach is meant for providing a ``memoryview`` for a C/C++ buffer not managed by Python. The user is responsible for managing the lifetime of the @@ -449,7 +449,7 @@ We can also use ``memoryview::from_memory`` for a simple 1D contiguous buffer: buffer, // buffer pointer sizeof(uint8_t) * 8 // buffer size ); - }) + }); .. versionchanged:: 2.6 ``memoryview::from_memory`` added. diff --git a/pybind11/docs/advanced/smart_ptrs.rst b/pybind11/docs/advanced/smart_ptrs.rst index 5a2220109..3c40ce123 100644 --- a/pybind11/docs/advanced/smart_ptrs.rst +++ b/pybind11/docs/advanced/smart_ptrs.rst @@ -157,7 +157,7 @@ specialized: PYBIND11_DECLARE_HOLDER_TYPE(T, SmartPtr); // Only needed if the type's `.get()` goes by another name - namespace pybind11 { namespace detail { + namespace PYBIND11_NAMESPACE { namespace detail { template struct holder_helper> { // <-- specialization static const T *get(const SmartPtr &p) { return p.getPointer(); } diff --git a/pybind11/docs/changelog.rst b/pybind11/docs/changelog.rst index b926b27af..add3fd66b 100644 --- a/pybind11/docs/changelog.rst +++ b/pybind11/docs/changelog.rst @@ -9,6 +9,364 @@ Starting with version 1.8.0, pybind11 releases use a `semantic versioning Changes will be added here periodically from the "Suggested changelog entry" block in pull request descriptions. + +Version 2.11.1 (July 17, 2023) +----------------------------- + +Changes: + +* ``PYBIND11_NO_ASSERT_GIL_HELD_INCREF_DECREF`` is now provided as an option + for disabling the default-on ``PyGILState_Check()``'s in + ``pybind11::handle``'s ``inc_ref()`` & ``dec_ref()``. + `#4753 `_ + +* ``PYBIND11_ASSERT_GIL_HELD_INCREF_DECREF`` was disabled for PyPy in general + (not just PyPy Windows). + `#4751 `_ + + +Version 2.11.0 (July 14, 2023) +----------------------------- + +New features: + +* The newly added ``pybind11::detail::is_move_constructible`` trait can be + specialized for cases in which ``std::is_move_constructible`` does not work + as needed. This is very similar to the long-established + ``pybind11::detail::is_copy_constructible``. + `#4631 `_ + +* Introduce ``recursive_container_traits``. + `#4623 `_ + +* ``pybind11/type_caster_pyobject_ptr.h`` was added to support automatic + wrapping of APIs that make use of ``PyObject *``. This header needs to + included explicitly (i.e. it is not included implicitly + with ``pybind/pybind11.h``). + `#4601 `_ + +* ``format_descriptor<>`` & ``npy_format_descriptor<>`` ``PyObject *`` + specializations were added. The latter enables ``py::array_t`` + to/from-python conversions. + `#4674 `_ + +* ``buffer_info`` gained an ``item_type_is_equivalent_to()`` member + function. + `#4674 `_ + +* The ``capsule`` API gained a user-friendly constructor + (``py::capsule(ptr, "name", dtor)``). + `#4720 `_ + +Changes: + +* ``PyGILState_Check()``'s in ``pybind11::handle``'s ``inc_ref()`` & + ``dec_ref()`` are now enabled by default again. + `#4246 `_ + +* ``py::initialize_interpreter()`` using ``PyConfig_InitPythonConfig()`` + instead of ``PyConfig_InitIsolatedConfig()``, to obtain complete + ``sys.path``. + `#4473 `_ + +* Cast errors now always include Python type information, even if + ``PYBIND11_DETAILED_ERROR_MESSAGES`` is not defined. This increases binary + sizes slightly (~1.5%) but the error messages are much more informative. + `#4463 `_ + +* The docstring generation for the ``std::array``-list caster was fixed. + Previously, signatures included the size of the list in a non-standard, + non-spec compliant way. The new format conforms to PEP 593. + **Tooling for processing the docstrings may need to be updated accordingly.** + `#4679 `_ + +* Setter return values (which are inaccessible for all practical purposes) are + no longer converted to Python (only to be discarded). + `#4621 `_ + +* Allow lambda specified to function definition to be ``noexcept(true)`` + in C++17. + `#4593 `_ + +* Get rid of recursive template instantiations for concatenating type + signatures on C++17 and higher. + `#4587 `_ + +* Compatibility with Python 3.12 (beta). Note that the minimum pybind11 + ABI version for Python 3.12 is version 5. (The default ABI version + for Python versions up to and including 3.11 is still version 4.). + `#4570 `_ + +* With ``PYBIND11_INTERNALS_VERSION 5`` (default for Python 3.12+), MSVC builds + use ``std::hash`` and ``std::equal_to`` + instead of string-based type comparisons. This resolves issues when binding + types defined in the unnamed namespace. + `#4319 `_ + +* Python exception ``__notes__`` (introduced with Python 3.11) are now added to + the ``error_already_set::what()`` output. + `#4678 `_ + +Build system improvements: + +* CMake 3.27 support was added, CMake 3.4 support was dropped. + FindPython will be used if ``FindPythonInterp`` is not present. + `#4719 `_ + +* Update clang-tidy to 15 in CI. + `#4387 `_ + +* Moved the linting framework over to Ruff. + `#4483 `_ + +* Skip ``lto`` checks and target generation when + ``CMAKE_INTERPROCEDURAL_OPTIMIZATION`` is defined. + `#4643 `_ + +* No longer inject ``-stdlib=libc++``, not needed for modern Pythons + (macOS 10.9+). + `#4639 `_ + +* PyPy 3.10 support was added, PyPy 3.7 support was dropped. + `#4728 `_ + +* Testing with Python 3.12 beta releases was added. + `#4713 `_ + + +Version 2.10.4 (Mar 16, 2023) +----------------------------- + +Changes: + +* ``python3 -m pybind11`` gained a ``--version`` option (prints the version and + exits). + `#4526 `_ + +Bug Fixes: + +* Fix a warning when pydebug is enabled on Python 3.11. + `#4461 `_ + +* Ensure ``gil_scoped_release`` RAII is non-copyable. + `#4490 `_ + +* Ensure the tests dir does not show up with new versions of setuptools. + `#4510 `_ + +* Better stacklevel for a warning in setuptools helpers. + `#4516 `_ + +Version 2.10.3 (Jan 3, 2023) +---------------------------- + +Changes: + +* Temporarily made our GIL status assertions (added in 2.10.2) disabled by + default (re-enable manually by defining + ``PYBIND11_ASSERT_GIL_HELD_INCREF_DECREF``, will be enabled in 2.11). + `#4432 `_ + +* Improved error messages when ``inc_ref``/``dec_ref`` are called with an + invalid GIL state. + `#4427 `_ + `#4436 `_ + +Bug Fixes: + +* Some minor touchups found by static analyzers. + `#4440 `_ + + +Version 2.10.2 (Dec 20, 2022) +----------------------------- + +Changes: + +* ``scoped_interpreter`` constructor taking ``PyConfig``. + `#4330 `_ + +* ``pybind11/eigen/tensor.h`` adds converters to and from ``Eigen::Tensor`` and + ``Eigen::TensorMap``. + `#4201 `_ + +* ``PyGILState_Check()``'s were integrated to ``pybind11::handle`` + ``inc_ref()`` & ``dec_ref()``. The added GIL checks are guarded by + ``PYBIND11_ASSERT_GIL_HELD_INCREF_DECREF``, which is the default only if + ``NDEBUG`` is not defined. (Made non-default in 2.10.3, will be active in 2.11) + `#4246 `_ + +* Add option for enable/disable enum members in docstring. + `#2768 `_ + +* Fixed typing of ``KeysView``, ``ValuesView`` and ``ItemsView`` in ``bind_map``. + `#4353 `_ + +Bug fixes: + +* Bug fix affecting only Python 3.6 under very specific, uncommon conditions: + move ``PyEval_InitThreads()`` call to the correct location. + `#4350 `_ + +* Fix segfault bug when passing foreign native functions to functional.h. + `#4254 `_ + +Build system improvements: + +* Support setting PYTHON_LIBRARIES manually for Windows ARM cross-compilation + (classic mode). + `#4406 `_ + +* Extend IPO/LTO detection for ICX (a.k.a IntelLLVM) compiler. + `#4402 `_ + +* Allow calling ``find_package(pybind11 CONFIG)`` multiple times from separate + directories in the same CMake project and properly link Python (new mode). + `#4401 `_ + +* ``multiprocessing_set_spawn`` in pytest fixture for added safety. + `#4377 `_ + +* Fixed a bug in two pybind11/tools cmake scripts causing "Unknown arguments specified" errors. + `#4327 `_ + + + +Version 2.10.1 (Oct 31, 2022) +----------------------------- + +This is the first version to fully support embedding the newly released Python 3.11. + +Changes: + +* Allow ``pybind11::capsule`` constructor to take null destructor pointers. + `#4221 `_ + +* ``embed.h`` was changed so that ``PYTHONPATH`` is used also with Python 3.11 + (established behavior). + `#4119 `_ + +* A ``PYBIND11_SIMPLE_GIL_MANAGEMENT`` option was added (cmake, C++ define), + along with many additional tests in ``test_gil_scoped.py``. The option may be + useful to try when debugging GIL-related issues, to determine if the more + complex default implementation is or is not to blame. See #4216 for + background. WARNING: Please be careful to not create ODR violations when + using the option: everything that is linked together with mutual symbol + visibility needs to be rebuilt. + `#4216 `_ + +* ``PYBIND11_EXPORT_EXCEPTION`` was made non-empty only under macOS. This makes + Linux builds safer, and enables the removal of warning suppression pragmas for + Windows. + `#4298 `_ + +Bug fixes: + +* Fixed a bug where ``UnicodeDecodeError`` was not propagated from various + ``py::str`` ctors when decoding surrogate utf characters. + `#4294 `_ + +* Revert perfect forwarding for ``make_iterator``. This broke at least one + valid use case. May revisit later. + `#4234 `_ + +* Fix support for safe casts to ``void*`` (regression in 2.10.0). + `#4275 `_ + +* Fix ``char8_t`` support (regression in 2.9). + `#4278 `_ + +* Unicode surrogate character in Python exception message leads to process + termination in ``error_already_set::what()``. + `#4297 `_ + +* Fix MSVC 2019 v.1924 & C++14 mode error for ``overload_cast``. + `#4188 `_ + +* Make augmented assignment operators non-const for the object-api. Behavior + was previously broken for augmented assignment operators. + `#4065 `_ + +* Add proper error checking to C++ bindings for Python list append and insert. + `#4208 `_ + +* Work-around for Nvidia's CUDA nvcc compiler in versions 11.4.0 - 11.8.0. + `#4220 `_ + +* A workaround for PyPy was added in the ``py::error_already_set`` + implementation, related to PR `#1895 `_ + released with v2.10.0. + `#4079 `_ + +* Fixed compiler errors when C++23 ``std::forward_like`` is available. + `#4136 `_ + +* Properly raise exceptions in contains methods (like when an object in unhashable). + `#4209 `_ + +* Further improve another error in exception handling. + `#4232 `_ + +* ``get_local_internals()`` was made compatible with + ``finalize_interpreter()``, fixing potential freezes during interpreter + finalization. + `#4192 `_ + +Performance and style: + +* Reserve space in set and STL map casters if possible. This will prevent + unnecessary rehashing / resizing by knowing the number of keys ahead of time + for Python to C++ casting. This improvement will greatly speed up the casting + of large unordered maps and sets. + `#4194 `_ + +* GIL RAII scopes are non-copyable to avoid potential bugs. + `#4183 `_ + +* Explicitly default all relevant ctors for pytypes in the ``PYBIND11_OBJECT`` + macros and enforce the clang-tidy checks ``modernize-use-equals-default`` in + macros as well. + `#4017 `_ + +* Optimize iterator advancement in C++ bindings. + `#4237 `_ + +* Use the modern ``PyObject_GenericGetDict`` and ``PyObject_GenericSetDict`` + for handling dynamic attribute dictionaries. + `#4106 `_ + +* Document that users should use ``PYBIND11_NAMESPACE`` instead of using ``pybind11`` when + opening namespaces. Using namespace declarations and namespace qualification + remain the same as ``pybind11``. This is done to ensure consistent symbol + visibility. + `#4098 `_ + +* Mark ``detail::forward_like`` as constexpr. + `#4147 `_ + +* Optimize unpacking_collector when processing ``arg_v`` arguments. + `#4219 `_ + +* Optimize casting C++ object to ``None``. + `#4269 `_ + + +Build system improvements: + +* CMake: revert overwrite behavior, now opt-in with ``PYBIND11_PYTHONLIBS_OVERRWRITE OFF``. + `#4195 `_ + +* Include a pkg-config file when installing pybind11, such as in the Python + package. + `#4077 `_ + +* Avoid stripping debug symbols when ``CMAKE_BUILD_TYPE`` is set to ``DEBUG`` + instead of ``Debug``. + `#4078 `_ + +* Followup to `#3948 `_, fixing vcpkg again. + `#4123 `_ + Version 2.10.0 (Jul 15, 2022) ----------------------------- diff --git a/pybind11/docs/classes.rst b/pybind11/docs/classes.rst index c0c53135b..4f2167dac 100644 --- a/pybind11/docs/classes.rst +++ b/pybind11/docs/classes.rst @@ -58,6 +58,16 @@ interactive Python session demonstrating this example is shown below: Static member functions can be bound in the same way using :func:`class_::def_static`. +.. note:: + + Binding C++ types in unnamed namespaces (also known as anonymous namespaces) + works reliably on many platforms, but not all. The `XFAIL_CONDITION` in + tests/test_unnamed_namespace_a.py encodes the currently known conditions. + For background see `#4319 `_. + If portability is a concern, it is therefore not recommended to bind C++ + types in unnamed namespaces. It will be safest to manually pick unique + namespace names. + Keyword and default arguments ============================= It is possible to specify keyword and default arguments using the syntax @@ -539,3 +549,7 @@ The ``name`` property returns the name of the enum value as a unicode string. ... By default, these are omitted to conserve space. + +.. warning:: + + Contrary to Python customs, enum values from the wrappers should not be compared using ``is``, but with ``==`` (see `#1177 `_ for background). diff --git a/pybind11/docs/compiling.rst b/pybind11/docs/compiling.rst index 2b543be0b..1fd098bec 100644 --- a/pybind11/docs/compiling.rst +++ b/pybind11/docs/compiling.rst @@ -241,7 +241,7 @@ extension module can be created with just a few lines of code: .. code-block:: cmake - cmake_minimum_required(VERSION 3.4...3.18) + cmake_minimum_required(VERSION 3.5...3.26) project(example LANGUAGES CXX) add_subdirectory(pybind11) @@ -261,6 +261,9 @@ PyPI integration, can be found in the [cmake_example]_ repository. .. versionchanged:: 2.6 CMake 3.4+ is required. +.. versionchanged:: 2.11 + CMake 3.5+ is required. + Further information can be found at :doc:`cmake/index`. pybind11_add_module @@ -495,7 +498,7 @@ You can use these targets to build complex applications. For example, the .. code-block:: cmake - cmake_minimum_required(VERSION 3.4) + cmake_minimum_required(VERSION 3.5...3.26) project(example LANGUAGES CXX) find_package(pybind11 REQUIRED) # or add_subdirectory(pybind11) @@ -553,7 +556,7 @@ information about usage in C++, see :doc:`/advanced/embedding`. .. code-block:: cmake - cmake_minimum_required(VERSION 3.4...3.18) + cmake_minimum_required(VERSION 3.5...3.26) project(example LANGUAGES CXX) find_package(pybind11 REQUIRED) # or add_subdirectory(pybind11) diff --git a/pybind11/docs/conf.py b/pybind11/docs/conf.py index 2da6773f4..6e24751e9 100644 --- a/pybind11/docs/conf.py +++ b/pybind11/docs/conf.py @@ -353,12 +353,11 @@ def prepare(app): f.write(contents) -def clean_up(app, exception): +def clean_up(app, exception): # noqa: ARG001 (DIR / "readme.rst").unlink() def setup(app): - # Add hook for building doxygen xml when needed app.connect("builder-inited", generate_doxygen_xml) diff --git a/pybind11/docs/faq.rst b/pybind11/docs/faq.rst index 28498e7df..1eb00efad 100644 --- a/pybind11/docs/faq.rst +++ b/pybind11/docs/faq.rst @@ -284,7 +284,8 @@ There are three possible solutions: COMPONENTS Interpreter Development)`` on modern CMake (3.12+, 3.15+ better, 3.18.2+ best). Pybind11 in these cases uses the new CMake FindPython instead of the old, deprecated search tools, and these modules are much better at - finding the correct Python. + finding the correct Python. If FindPythonLibs/Interp are not available + (CMake 3.27+), then this will be ignored and FindPython will be used. 3. Set ``PYBIND11_NOPYTHON`` to ``TRUE``. Pybind11 will not search for Python. However, you will have to use the target-based system, and do more setup yourself, because it does not know about or include things that depend on diff --git a/pybind11/docs/release.rst b/pybind11/docs/release.rst index e761cdf7a..4950c3b88 100644 --- a/pybind11/docs/release.rst +++ b/pybind11/docs/release.rst @@ -33,10 +33,12 @@ If you don't have nox, you should either use ``pipx run nox`` instead, or use - Run ``nox -s tests_packaging`` to ensure this was done correctly. - Ensure that all the information in ``setup.cfg`` is up-to-date, like supported Python versions. - - Add release date in ``docs/changelog.rst``. - - Check to make sure - `needs-changelog `_ - issues are entered in the changelog (clear the label when done). + - Add release date in ``docs/changelog.rst`` and integrate the output of + ``nox -s make_changelog``. + - Note that the ``make_changelog`` command inspects + `needs changelog `_. + - Manually clear the ``needs changelog`` labels using the GitHub web + interface (very easy: start by clicking the link above). - ``git add`` and ``git commit``, ``git push``. **Ensure CI passes**. (If it fails due to a known flake issue, either ignore or restart CI.) - Add a release branch if this is a new minor version, or update the existing release branch if it is a patch version diff --git a/pybind11/docs/upgrade.rst b/pybind11/docs/upgrade.rst index 6a9db2d08..b13d21f5e 100644 --- a/pybind11/docs/upgrade.rst +++ b/pybind11/docs/upgrade.rst @@ -8,6 +8,20 @@ to a new version. But it goes into more detail. This includes things like deprecated APIs and their replacements, build system changes, general code modernization and other useful information. +.. _upgrade-guide-2.11: + +v2.11 +===== + +* The minimum version of CMake is now 3.5. A future version will likely move to + requiring something like CMake 3.15. Note that CMake 3.27 is removing the + long-deprecated support for ``FindPythonInterp`` if you set 3.27 as the + minimum or maximum supported version. To prepare for that future, CMake 3.15+ + using ``FindPython`` or setting ``PYBIND11_FINDPYTHON`` is highly recommended, + otherwise pybind11 will automatically switch to using ``FindPython`` if + ``FindPythonInterp`` is not available. + + .. _upgrade-guide-2.9: v2.9 diff --git a/pybind11/include/pybind11/attr.h b/pybind11/include/pybind11/attr.h index db7cd8eff..1044db94d 100644 --- a/pybind11/include/pybind11/attr.h +++ b/pybind11/include/pybind11/attr.h @@ -26,6 +26,9 @@ struct is_method { explicit is_method(const handle &c) : class_(c) {} }; +/// Annotation for setters +struct is_setter {}; + /// Annotation for operators struct is_operator {}; @@ -188,8 +191,8 @@ struct argument_record { struct function_record { function_record() : is_constructor(false), is_new_style_constructor(false), is_stateless(false), - is_operator(false), is_method(false), has_args(false), has_kwargs(false), - prepend(false) {} + is_operator(false), is_method(false), is_setter(false), has_args(false), + has_kwargs(false), prepend(false) {} /// Function name char *name = nullptr; /* why no C++ strings? They generate heavier code.. */ @@ -230,6 +233,9 @@ struct function_record { /// True if this is a method bool is_method : 1; + /// True if this is a setter + bool is_setter : 1; + /// True if the function has a '*args' argument bool has_args : 1; @@ -399,7 +405,7 @@ struct process_attribute : process_attribute_default { template <> struct process_attribute : process_attribute_default { static void init(const char *d, function_record *r) { r->doc = const_cast(d); } - static void init(const char *d, type_record *r) { r->doc = const_cast(d); } + static void init(const char *d, type_record *r) { r->doc = d; } }; template <> struct process_attribute : process_attribute {}; @@ -426,6 +432,12 @@ struct process_attribute : process_attribute_default { } }; +/// Process an attribute which indicates that this function is a setter +template <> +struct process_attribute : process_attribute_default { + static void init(const is_setter &, function_record *r) { r->is_setter = true; } +}; + /// Process an attribute which indicates the parent scope of a method template <> struct process_attribute : process_attribute_default { diff --git a/pybind11/include/pybind11/buffer_info.h b/pybind11/include/pybind11/buffer_info.h index 06120d556..b99ee8bef 100644 --- a/pybind11/include/pybind11/buffer_info.h +++ b/pybind11/include/pybind11/buffer_info.h @@ -37,6 +37,9 @@ inline std::vector f_strides(const std::vector &shape, ssize_t return strides; } +template +struct compare_buffer_info; + PYBIND11_NAMESPACE_END(detail) /// Information record describing a Python buffer object @@ -150,6 +153,17 @@ struct buffer_info { Py_buffer *view() const { return m_view; } Py_buffer *&view() { return m_view; } + /* True if the buffer item type is equivalent to `T`. */ + // To define "equivalent" by example: + // `buffer_info::item_type_is_equivalent_to(b)` and + // `buffer_info::item_type_is_equivalent_to(b)` may both be true + // on some platforms, but `int` and `unsigned` will never be equivalent. + // For the ground truth, please inspect `detail::compare_buffer_info<>`. + template + bool item_type_is_equivalent_to() const { + return detail::compare_buffer_info::compare(*this); + } + private: struct private_ctr_tag {}; @@ -170,9 +184,10 @@ private: PYBIND11_NAMESPACE_BEGIN(detail) -template +template struct compare_buffer_info { static bool compare(const buffer_info &b) { + // NOLINTNEXTLINE(bugprone-sizeof-expression) Needed for `PyObject *` return b.format == format_descriptor::format() && b.itemsize == (ssize_t) sizeof(T); } }; diff --git a/pybind11/include/pybind11/cast.h b/pybind11/include/pybind11/cast.h index a0e32281b..db3934118 100644 --- a/pybind11/include/pybind11/cast.h +++ b/pybind11/include/pybind11/cast.h @@ -29,6 +29,9 @@ #include PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE) + +PYBIND11_WARNING_DISABLE_MSVC(4127) + PYBIND11_NAMESPACE_BEGIN(detail) template @@ -88,7 +91,8 @@ public: template >::value, \ - int> = 0> \ + int> \ + = 0> \ static ::pybind11::handle cast( \ T_ *src, ::pybind11::return_value_policy policy, ::pybind11::handle parent) { \ if (!src) \ @@ -248,7 +252,7 @@ public: return false; } static handle cast(T, return_value_policy /* policy */, handle /* parent */) { - return none().inc_ref(); + return none().release(); } PYBIND11_TYPE_CASTER(T, const_name("None")); }; @@ -291,7 +295,7 @@ public: if (ptr) { return capsule(ptr).release(); } - return none().inc_ref(); + return none().release(); } template @@ -389,7 +393,7 @@ struct string_caster { // For UTF-8 we avoid the need for a temporary `bytes` object by using // `PyUnicode_AsUTF8AndSize`. - if (PYBIND11_SILENCE_MSVC_C4127(UTF_N == 8)) { + if (UTF_N == 8) { Py_ssize_t size = -1; const auto *buffer = reinterpret_cast(PyUnicode_AsUTF8AndSize(load_src.ptr(), &size)); @@ -416,7 +420,7 @@ struct string_caster { = reinterpret_cast(PYBIND11_BYTES_AS_STRING(utfNbytes.ptr())); size_t length = (size_t) PYBIND11_BYTES_SIZE(utfNbytes.ptr()) / sizeof(CharT); // Skip BOM for UTF-16/32 - if (PYBIND11_SILENCE_MSVC_C4127(UTF_N > 8)) { + if (UTF_N > 8) { buffer++; length--; } @@ -537,7 +541,7 @@ public: static handle cast(const CharT *src, return_value_policy policy, handle parent) { if (src == nullptr) { - return pybind11::none().inc_ref(); + return pybind11::none().release(); } return StringCaster::cast(StringType(src), policy, parent); } @@ -572,7 +576,7 @@ public: // figure out how long the first encoded character is in bytes to distinguish between these // two errors. We also allow want to allow unicode characters U+0080 through U+00FF, as // those can fit into a single char value. - if (PYBIND11_SILENCE_MSVC_C4127(StringCaster::UTF_N == 8) && str_len > 1 && str_len <= 4) { + if (StringCaster::UTF_N == 8 && str_len > 1 && str_len <= 4) { auto v0 = static_cast(value[0]); // low bits only: 0-127 // 0b110xxxxx - start of 2-byte sequence @@ -598,7 +602,7 @@ public: // UTF-16 is much easier: we can only have a surrogate pair for values above U+FFFF, thus a // surrogate pair with total length 2 instantly indicates a range error (but not a "your // string was too long" error). - else if (PYBIND11_SILENCE_MSVC_C4127(StringCaster::UTF_N == 16) && str_len == 2) { + else if (StringCaster::UTF_N == 16 && str_len == 2) { one_char = static_cast(value[0]); if (one_char >= 0xD800 && one_char < 0xE000) { throw value_error("Character code point not in range(0x10000)"); @@ -960,7 +964,7 @@ struct move_always< enable_if_t< all_of, negation>, - std::is_move_constructible, + is_move_constructible, std::is_same>().operator T &()), T &>>::value>> : std::true_type {}; template @@ -971,7 +975,7 @@ struct move_if_unreferenced< enable_if_t< all_of, negation>, - std::is_move_constructible, + is_move_constructible, std::is_same>().operator T &()), T &>>::value>> : std::true_type {}; template @@ -1013,11 +1017,14 @@ type_caster &load_type(type_caster &conv, const handle &ha "Internal error: type_caster should only be used for C++ types"); if (!conv.load(handle, true)) { #if !defined(PYBIND11_DETAILED_ERROR_MESSAGES) - throw cast_error("Unable to cast Python instance to C++ type (#define " - "PYBIND11_DETAILED_ERROR_MESSAGES or compile in debug mode for details)"); + throw cast_error( + "Unable to cast Python instance of type " + + str(type::handle_of(handle)).cast() + + " to C++ type '?' (#define " + "PYBIND11_DETAILED_ERROR_MESSAGES or compile in debug mode for details)"); #else throw cast_error("Unable to cast Python instance of type " - + (std::string) str(type::handle_of(handle)) + " to C++ type '" + + str(type::handle_of(handle)).cast() + " to C++ type '" + type_id() + "'"); #endif } @@ -1034,7 +1041,11 @@ make_caster load_type(const handle &handle) { PYBIND11_NAMESPACE_END(detail) // pytype -> C++ type -template ::value, int> = 0> +template ::value + && !detail::is_same_ignoring_cvref::value, + int> + = 0> T cast(const handle &handle) { using namespace detail; static_assert(!cast_is_temporary_value_reference::value, @@ -1048,6 +1059,34 @@ T cast(const handle &handle) { return T(reinterpret_borrow(handle)); } +// Note that `cast(obj)` increments the reference count of `obj`. +// This is necessary for the case that `obj` is a temporary, and could +// not possibly be different, given +// 1. the established convention that the passed `handle` is borrowed, and +// 2. we don't want to force all generic code using `cast()` to special-case +// handling of `T` = `PyObject *` (to increment the reference count there). +// It is the responsibility of the caller to ensure that the reference count +// is decremented. +template ::value + && detail::is_same_ignoring_cvref::value, + int> + = 0> +T cast(Handle &&handle) { + return handle.inc_ref().ptr(); +} +// To optimize way an inc_ref/dec_ref cycle: +template ::value + && detail::is_same_ignoring_cvref::value, + int> + = 0> +T cast(Object &&obj) { + return obj.release().ptr(); +} + // C++ type -> py::object template ::value, int> = 0> object cast(T &&value, @@ -1081,12 +1120,13 @@ detail::enable_if_t::value, T> move(object &&obj) { if (obj.ref_count() > 1) { #if !defined(PYBIND11_DETAILED_ERROR_MESSAGES) throw cast_error( - "Unable to cast Python instance to C++ rvalue: instance has multiple references" - " (#define PYBIND11_DETAILED_ERROR_MESSAGES or compile in debug mode for details)"); + "Unable to cast Python " + str(type::handle_of(obj)).cast() + + " instance to C++ rvalue: instance has multiple references" + " (#define PYBIND11_DETAILED_ERROR_MESSAGES or compile in debug mode for details)"); #else - throw cast_error("Unable to move from Python " + (std::string) str(type::handle_of(obj)) - + " instance to C++ " + type_id() - + " instance: instance has multiple references"); + throw cast_error("Unable to move from Python " + + str(type::handle_of(obj)).cast() + " instance to C++ " + + type_id() + " instance: instance has multiple references"); #endif } @@ -1179,11 +1219,9 @@ enable_if_t::value, T> cast_safe(object &&) pybind11_fail("Internal error: cast_safe fallback invoked"); } template -enable_if_t>::value, void> cast_safe(object &&) {} +enable_if_t::value, void> cast_safe(object &&) {} template -enable_if_t, - std::is_same>>::value, - T> +enable_if_t, std::is_void>::value, T> cast_safe(object &&o) { return pybind11::cast(std::move(o)); } @@ -1193,9 +1231,10 @@ PYBIND11_NAMESPACE_END(detail) // The overloads could coexist, i.e. the #if is not strictly speaking needed, // but it is an easy minor optimization. #if !defined(PYBIND11_DETAILED_ERROR_MESSAGES) -inline cast_error cast_error_unable_to_convert_call_arg() { - return cast_error("Unable to convert call argument to Python object (#define " - "PYBIND11_DETAILED_ERROR_MESSAGES or compile in debug mode for details)"); +inline cast_error cast_error_unable_to_convert_call_arg(const std::string &name) { + return cast_error("Unable to convert call argument '" + name + + "' to Python object (#define " + "PYBIND11_DETAILED_ERROR_MESSAGES or compile in debug mode for details)"); } #else inline cast_error cast_error_unable_to_convert_call_arg(const std::string &name, @@ -1218,7 +1257,7 @@ tuple make_tuple(Args &&...args_) { for (size_t i = 0; i < args.size(); i++) { if (!args[i]) { #if !defined(PYBIND11_DETAILED_ERROR_MESSAGES) - throw cast_error_unable_to_convert_call_arg(); + throw cast_error_unable_to_convert_call_arg(std::to_string(i)); #else std::array argtypes{{type_id()...}}; throw cast_error_unable_to_convert_call_arg(std::to_string(i), argtypes[i]); @@ -1508,7 +1547,7 @@ private: detail::make_caster::cast(std::forward(x), policy, {})); if (!o) { #if !defined(PYBIND11_DETAILED_ERROR_MESSAGES) - throw cast_error_unable_to_convert_call_arg(); + throw cast_error_unable_to_convert_call_arg(std::to_string(args_list.size())); #else throw cast_error_unable_to_convert_call_arg(std::to_string(args_list.size()), type_id()); @@ -1540,12 +1579,12 @@ private: } if (!a.value) { #if !defined(PYBIND11_DETAILED_ERROR_MESSAGES) - throw cast_error_unable_to_convert_call_arg(); + throw cast_error_unable_to_convert_call_arg(a.name); #else throw cast_error_unable_to_convert_call_arg(a.name, a.type); #endif } - m_kwargs[a.name] = a.value; + m_kwargs[a.name] = std::move(a.value); } void process(list & /*args_list*/, detail::kwargs_proxy kp) { diff --git a/pybind11/include/pybind11/detail/class.h b/pybind11/include/pybind11/detail/class.h index 42720f844..bc2b40c50 100644 --- a/pybind11/include/pybind11/detail/class.h +++ b/pybind11/include/pybind11/detail/class.h @@ -55,6 +55,9 @@ extern "C" inline int pybind11_static_set(PyObject *self, PyObject *obj, PyObjec return PyProperty_Type.tp_descr_set(self, cls, value); } +// Forward declaration to use in `make_static_property_type()` +inline void enable_dynamic_attributes(PyHeapTypeObject *heap_type); + /** A `static_property` is the same as a `property` but the `__get__()` and `__set__()` methods are modified to always use the object type instead of a concrete instance. Return value: New reference. */ @@ -87,6 +90,13 @@ inline PyTypeObject *make_static_property_type() { pybind11_fail("make_static_property_type(): failure in PyType_Ready()!"); } +# if PY_VERSION_HEX >= 0x030C0000 + // PRE 3.12 FEATURE FREEZE. PLEASE REVIEW AFTER FREEZE. + // Since Python-3.12 property-derived types are required to + // have dynamic attributes (to set `__doc__`) + enable_dynamic_attributes(heap_type); +# endif + setattr((PyObject *) type, "__module__", str("pybind11_builtins")); PYBIND11_SET_OLDPY_QUALNAME(type, name_obj); @@ -435,9 +445,17 @@ inline void clear_instance(PyObject *self) { /// Instance destructor function for all pybind11 types. It calls `type_info.dealloc` /// to destroy the C++ object itself, while the rest is Python bookkeeping. extern "C" inline void pybind11_object_dealloc(PyObject *self) { + auto *type = Py_TYPE(self); + + // If this is a GC tracked object, untrack it first + // Note that the track call is implicitly done by the + // default tp_alloc, which we never override. + if (PyType_HasFeature(type, Py_TPFLAGS_HAVE_GC) != 0) { + PyObject_GC_UnTrack(self); + } + clear_instance(self); - auto *type = Py_TYPE(self); type->tp_free(self); #if PY_VERSION_HEX < 0x03080000 @@ -502,31 +520,6 @@ inline PyObject *make_object_base_type(PyTypeObject *metaclass) { return (PyObject *) heap_type; } -/// dynamic_attr: Support for `d = instance.__dict__`. -extern "C" inline PyObject *pybind11_get_dict(PyObject *self, void *) { - PyObject *&dict = *_PyObject_GetDictPtr(self); - if (!dict) { - dict = PyDict_New(); - } - Py_XINCREF(dict); - return dict; -} - -/// dynamic_attr: Support for `instance.__dict__ = dict()`. -extern "C" inline int pybind11_set_dict(PyObject *self, PyObject *new_dict, void *) { - if (!PyDict_Check(new_dict)) { - PyErr_Format(PyExc_TypeError, - "__dict__ must be set to a dictionary, not a '%.200s'", - get_fully_qualified_tp_name(Py_TYPE(new_dict)).c_str()); - return -1; - } - PyObject *&dict = *_PyObject_GetDictPtr(self); - Py_INCREF(new_dict); - Py_CLEAR(dict); - dict = new_dict; - return 0; -} - /// dynamic_attr: Allow the garbage collector to traverse the internal instance `__dict__`. extern "C" inline int pybind11_traverse(PyObject *self, visitproc visit, void *arg) { PyObject *&dict = *_PyObject_GetDictPtr(self); @@ -558,9 +551,17 @@ inline void enable_dynamic_attributes(PyHeapTypeObject *heap_type) { type->tp_traverse = pybind11_traverse; type->tp_clear = pybind11_clear; - static PyGetSetDef getset[] = { - {const_cast("__dict__"), pybind11_get_dict, pybind11_set_dict, nullptr, nullptr}, - {nullptr, nullptr, nullptr, nullptr, nullptr}}; + static PyGetSetDef getset[] = {{ +#if PY_VERSION_HEX < 0x03070000 + const_cast("__dict__"), +#else + "__dict__", +#endif + PyObject_GenericGetDict, + PyObject_GenericSetDict, + nullptr, + nullptr}, + {nullptr, nullptr, nullptr, nullptr, nullptr}}; type->tp_getset = getset; } diff --git a/pybind11/include/pybind11/detail/common.h b/pybind11/include/pybind11/detail/common.h index 1da323f31..31a54c773 100644 --- a/pybind11/include/pybind11/detail/common.h +++ b/pybind11/include/pybind11/detail/common.h @@ -10,15 +10,76 @@ #pragma once #define PYBIND11_VERSION_MAJOR 2 -#define PYBIND11_VERSION_MINOR 10 -#define PYBIND11_VERSION_PATCH 0 +#define PYBIND11_VERSION_MINOR 11 +#define PYBIND11_VERSION_PATCH 1 // Similar to Python's convention: https://docs.python.org/3/c-api/apiabiversion.html // Additional convention: 0xD = dev -#define PYBIND11_VERSION_HEX 0x020A0000 +#define PYBIND11_VERSION_HEX 0x020B0100 -#define PYBIND11_NAMESPACE_BEGIN(name) namespace name { -#define PYBIND11_NAMESPACE_END(name) } +// Define some generic pybind11 helper macros for warning management. +// +// Note that compiler-specific push/pop pairs are baked into the +// PYBIND11_NAMESPACE_BEGIN/PYBIND11_NAMESPACE_END pair of macros. Therefore manual +// PYBIND11_WARNING_PUSH/PYBIND11_WARNING_POP are usually only needed in `#include` sections. +// +// If you find you need to suppress a warning, please try to make the suppression as local as +// possible using these macros. Please also be sure to push/pop with the pybind11 macros. Please +// only use compiler specifics if you need to check specific versions, e.g. Apple Clang vs. vanilla +// Clang. +#if defined(_MSC_VER) +# define PYBIND11_COMPILER_MSVC +# define PYBIND11_PRAGMA(...) __pragma(__VA_ARGS__) +# define PYBIND11_WARNING_PUSH PYBIND11_PRAGMA(warning(push)) +# define PYBIND11_WARNING_POP PYBIND11_PRAGMA(warning(pop)) +#elif defined(__INTEL_COMPILER) +# define PYBIND11_COMPILER_INTEL +# define PYBIND11_PRAGMA(...) _Pragma(#__VA_ARGS__) +# define PYBIND11_WARNING_PUSH PYBIND11_PRAGMA(warning push) +# define PYBIND11_WARNING_POP PYBIND11_PRAGMA(warning pop) +#elif defined(__clang__) +# define PYBIND11_COMPILER_CLANG +# define PYBIND11_PRAGMA(...) _Pragma(#__VA_ARGS__) +# define PYBIND11_WARNING_PUSH PYBIND11_PRAGMA(clang diagnostic push) +# define PYBIND11_WARNING_POP PYBIND11_PRAGMA(clang diagnostic push) +#elif defined(__GNUC__) +# define PYBIND11_COMPILER_GCC +# define PYBIND11_PRAGMA(...) _Pragma(#__VA_ARGS__) +# define PYBIND11_WARNING_PUSH PYBIND11_PRAGMA(GCC diagnostic push) +# define PYBIND11_WARNING_POP PYBIND11_PRAGMA(GCC diagnostic pop) +#endif + +#ifdef PYBIND11_COMPILER_MSVC +# define PYBIND11_WARNING_DISABLE_MSVC(name) PYBIND11_PRAGMA(warning(disable : name)) +#else +# define PYBIND11_WARNING_DISABLE_MSVC(name) +#endif + +#ifdef PYBIND11_COMPILER_CLANG +# define PYBIND11_WARNING_DISABLE_CLANG(name) PYBIND11_PRAGMA(clang diagnostic ignored name) +#else +# define PYBIND11_WARNING_DISABLE_CLANG(name) +#endif + +#ifdef PYBIND11_COMPILER_GCC +# define PYBIND11_WARNING_DISABLE_GCC(name) PYBIND11_PRAGMA(GCC diagnostic ignored name) +#else +# define PYBIND11_WARNING_DISABLE_GCC(name) +#endif + +#ifdef PYBIND11_COMPILER_INTEL +# define PYBIND11_WARNING_DISABLE_INTEL(name) PYBIND11_PRAGMA(warning disable name) +#else +# define PYBIND11_WARNING_DISABLE_INTEL(name) +#endif + +#define PYBIND11_NAMESPACE_BEGIN(name) \ + namespace name { \ + PYBIND11_WARNING_PUSH + +#define PYBIND11_NAMESPACE_END(name) \ + PYBIND11_WARNING_POP \ + } // Robust support for some features and loading modules compiled against different pybind versions // requires forcing hidden visibility on pybind code, so we enforce this by setting the attribute @@ -96,13 +157,10 @@ #endif #if !defined(PYBIND11_EXPORT_EXCEPTION) -# ifdef __MINGW32__ -// workaround for: -// error: 'dllexport' implies default visibility, but xxx has already been declared with a -// different visibility -# define PYBIND11_EXPORT_EXCEPTION -# else +# if defined(__apple_build_version__) # define PYBIND11_EXPORT_EXCEPTION PYBIND11_EXPORT +# else +# define PYBIND11_EXPORT_EXCEPTION # endif #endif @@ -154,9 +212,9 @@ /// Include Python header, disable linking to pythonX_d.lib on Windows in debug mode #if defined(_MSC_VER) -# pragma warning(push) +PYBIND11_WARNING_PUSH +PYBIND11_WARNING_DISABLE_MSVC(4505) // C4505: 'PySlice_GetIndicesEx': unreferenced local function has been removed (PyPy only) -# pragma warning(disable : 4505) # if defined(_DEBUG) && !defined(Py_DEBUG) // Workaround for a VS 2022 issue. // NOTE: This workaround knowingly violates the Python.h include order requirement: @@ -205,11 +263,8 @@ # endif #endif -#if defined(__cpp_lib_char8_t) && __cpp_lib_char8_t >= 201811L -# define PYBIND11_HAS_U8STRING -#endif - #include +// Reminder: WITH_THREAD is always defined if PY_VERSION_HEX >= 0x03070000 #if PY_VERSION_HEX < 0x03060000 # error "PYTHON < 3.6 IS UNSUPPORTED. pybind11 v2.9 was the last to support Python 2 and 3.5." #endif @@ -233,12 +288,16 @@ # undef copysign #endif +#if defined(PYPY_VERSION) && !defined(PYBIND11_SIMPLE_GIL_MANAGEMENT) +# define PYBIND11_SIMPLE_GIL_MANAGEMENT +#endif + #if defined(_MSC_VER) # if defined(PYBIND11_DEBUG_MARKER) # define _DEBUG # undef PYBIND11_DEBUG_MARKER # endif -# pragma warning(pop) +PYBIND11_WARNING_POP #endif #include @@ -259,6 +318,17 @@ # endif #endif +// Must be after including or one of the other headers specified by the standard +#if defined(__cpp_lib_char8_t) && __cpp_lib_char8_t >= 201811L +# define PYBIND11_HAS_U8STRING +#endif + +// See description of PR #4246: +#if !defined(PYBIND11_NO_ASSERT_GIL_HELD_INCREF_DECREF) && !defined(NDEBUG) \ + && !defined(PYPY_VERSION) && !defined(PYBIND11_ASSERT_GIL_HELD_INCREF_DECREF) +# define PYBIND11_ASSERT_GIL_HELD_INCREF_DECREF +#endif + // #define PYBIND11_STR_LEGACY_PERMISSIVE // If DEFINED, pybind11::str can hold PyUnicodeObject or PyBytesObject // (probably surprising and never documented, but this was the @@ -363,7 +433,7 @@ /** \rst This macro creates the entry point that will be invoked when the Python interpreter - imports an extension module. The module name is given as the fist argument and it + imports an extension module. The module name is given as the first argument and it should not be in quotes. The second macro argument defines a variable of type `py::module_` which can be used to initialize the module. @@ -588,6 +658,10 @@ template using remove_cvref_t = typename remove_cvref::type; #endif +/// Example usage: is_same_ignoring_cvref::value +template +using is_same_ignoring_cvref = std::is_same, U>; + /// Index sequences #if defined(PYBIND11_CPP14) using std::index_sequence; @@ -681,7 +755,16 @@ template struct remove_class { using type = R(A...); }; - +#ifdef __cpp_noexcept_function_type +template +struct remove_class { + using type = R(A...); +}; +template +struct remove_class { + using type = R(A...); +}; +#endif /// Helper template to strip away type modifiers template struct intrinsic_type { @@ -898,12 +981,6 @@ using expand_side_effects = bool[]; PYBIND11_NAMESPACE_END(detail) -#if defined(_MSC_VER) -# pragma warning(push) -# pragma warning(disable : 4275) -// warning C4275: An exported class was derived from a class that wasn't exported. -// Can be ignored when derived from a STL class. -#endif /// C++ bindings of builtin Python exceptions class PYBIND11_EXPORT_EXCEPTION builtin_exception : public std::runtime_error { public: @@ -911,9 +988,6 @@ public: /// Set the error using the Python C API virtual void set_error() const = 0; }; -#if defined(_MSC_VER) -# pragma warning(pop) -#endif #define PYBIND11_RUNTIME_EXCEPTION(name, type) \ class PYBIND11_EXPORT_EXCEPTION name : public builtin_exception { \ @@ -948,6 +1022,15 @@ PYBIND11_RUNTIME_EXCEPTION(reference_cast_error, PyExc_RuntimeError) /// Used in template struct format_descriptor {}; +template +struct format_descriptor< + T, + detail::enable_if_t::value>> { + static constexpr const char c = 'O'; + static constexpr const char value[2] = {c, '\0'}; + static std::string format() { return std::string(1, c); } +}; + PYBIND11_NAMESPACE_BEGIN(detail) // Returns the index of the given type in the type char array below, and in the list in numpy.h // The order here is: bool; 8 ints ((signed,unsigned)x(8,16,32,64)bits); float,double,long double; @@ -1033,12 +1116,7 @@ PYBIND11_NAMESPACE_END(detail) /// - regular: static_cast(&Class::func) /// - sweet: overload_cast(&Class::func) template -# if (defined(_MSC_VER) && _MSC_VER < 1920) /* MSVC 2017 */ \ - || (defined(__clang__) && __clang_major__ == 5) -static constexpr detail::overload_cast_impl overload_cast = {}; -# else -static constexpr detail::overload_cast_impl overload_cast; -# endif +static constexpr detail::overload_cast_impl overload_cast{}; #endif /// Const member function selector for overload_cast @@ -1147,20 +1225,28 @@ constexpr # define PYBIND11_WORKAROUND_INCORRECT_GCC_UNUSED_BUT_SET_PARAMETER(...) #endif -#if defined(_MSC_VER) // All versions (as of July 2021). - -// warning C4127: Conditional expression is constant -constexpr inline bool silence_msvc_c4127(bool cond) { return cond; } - -# define PYBIND11_SILENCE_MSVC_C4127(...) ::pybind11::detail::silence_msvc_c4127(__VA_ARGS__) - -#else -# define PYBIND11_SILENCE_MSVC_C4127(...) __VA_ARGS__ +#if defined(__clang__) \ + && (defined(__apple_build_version__) /* AppleClang 13.0.0.13000029 was the only data point \ + available. */ \ + || (__clang_major__ >= 7 \ + && __clang_major__ <= 12) /* Clang 3, 5, 13, 14, 15 do not generate the warning. */ \ + ) +# define PYBIND11_DETECTED_CLANG_WITH_MISLEADING_CALL_STD_MOVE_EXPLICITLY_WARNING +// Example: +// tests/test_kwargs_and_defaults.cpp:46:68: error: local variable 'args' will be copied despite +// being returned by name [-Werror,-Wreturn-std-move] +// m.def("args_function", [](py::args args) -> py::tuple { return args; }); +// ^~~~ +// test_kwargs_and_defaults.cpp:46:68: note: call 'std::move' explicitly to avoid copying +// m.def("args_function", [](py::args args) -> py::tuple { return args; }); +// ^~~~ +// std::move(args) #endif // Pybind offers detailed error messages by default for all builts that are debug (through the -// negation of ndebug). This can also be manually enabled by users, for any builds, through -// defining PYBIND11_DETAILED_ERROR_MESSAGES. +// negation of NDEBUG). This can also be manually enabled by users, for any builds, through +// defining PYBIND11_DETAILED_ERROR_MESSAGES. This information is primarily useful for those +// who are writing (as opposed to merely using) libraries that use pybind11. #if !defined(PYBIND11_DETAILED_ERROR_MESSAGES) && !defined(NDEBUG) # define PYBIND11_DETAILED_ERROR_MESSAGES #endif diff --git a/pybind11/include/pybind11/detail/descr.h b/pybind11/include/pybind11/detail/descr.h index e7a5e2c14..635614b0d 100644 --- a/pybind11/include/pybind11/detail/descr.h +++ b/pybind11/include/pybind11/detail/descr.h @@ -143,11 +143,24 @@ constexpr descr concat(const descr &descr) { return descr; } +#ifdef __cpp_fold_expressions +template +constexpr descr operator,(const descr &a, + const descr &b) { + return a + const_name(", ") + b; +} + +template +constexpr auto concat(const descr &d, const Args &...args) { + return (d, ..., args); +} +#else template constexpr auto concat(const descr &d, const Args &...args) -> decltype(std::declval>() + concat(args...)) { return d + const_name(", ") + concat(args...); } +#endif template constexpr descr type_descr(const descr &descr) { diff --git a/pybind11/include/pybind11/detail/init.h b/pybind11/include/pybind11/detail/init.h index 05f4fe54a..e21171688 100644 --- a/pybind11/include/pybind11/detail/init.h +++ b/pybind11/include/pybind11/detail/init.h @@ -12,6 +12,9 @@ #include "class.h" PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE) + +PYBIND11_WARNING_DISABLE_MSVC(4127) + PYBIND11_NAMESPACE_BEGIN(detail) template <> @@ -115,7 +118,7 @@ template void construct(value_and_holder &v_h, Cpp *ptr, bool need_alias) { PYBIND11_WORKAROUND_INCORRECT_MSVC_C4100(need_alias); no_nullptr(ptr); - if (PYBIND11_SILENCE_MSVC_C4127(Class::has_alias) && need_alias && !is_alias(ptr)) { + if (Class::has_alias && need_alias && !is_alias(ptr)) { // We're going to try to construct an alias by moving the cpp type. Whether or not // that succeeds, we still need to destroy the original cpp pointer (either the // moved away leftover, if the alias construction works, or the value itself if we @@ -156,7 +159,7 @@ void construct(value_and_holder &v_h, Holder holder, bool need_alias) { auto *ptr = holder_helper>::get(holder); no_nullptr(ptr); // If we need an alias, check that the held pointer is actually an alias instance - if (PYBIND11_SILENCE_MSVC_C4127(Class::has_alias) && need_alias && !is_alias(ptr)) { + if (Class::has_alias && need_alias && !is_alias(ptr)) { throw type_error("pybind11::init(): construction failed: returned holder-wrapped instance " "is not an alias instance"); } @@ -172,9 +175,9 @@ void construct(value_and_holder &v_h, Holder holder, bool need_alias) { template void construct(value_and_holder &v_h, Cpp &&result, bool need_alias) { PYBIND11_WORKAROUND_INCORRECT_MSVC_C4100(need_alias); - static_assert(std::is_move_constructible>::value, + static_assert(is_move_constructible>::value, "pybind11::init() return-by-value factory function requires a movable class"); - if (PYBIND11_SILENCE_MSVC_C4127(Class::has_alias) && need_alias) { + if (Class::has_alias && need_alias) { construct_alias_from_cpp(is_alias_constructible{}, v_h, std::move(result)); } else { v_h.value_ptr() = new Cpp(std::move(result)); @@ -187,7 +190,7 @@ void construct(value_and_holder &v_h, Cpp &&result, bool need_alias) { template void construct(value_and_holder &v_h, Alias &&result, bool) { static_assert( - std::is_move_constructible>::value, + is_move_constructible>::value, "pybind11::init() return-by-alias-value factory function requires a movable alias class"); v_h.value_ptr() = new Alias(std::move(result)); } @@ -206,10 +209,11 @@ struct constructor { extra...); } - template , Args...>::value, - int> = 0> + template < + typename Class, + typename... Extra, + enable_if_t, Args...>::value, int> + = 0> static void execute(Class &cl, const Extra &...extra) { cl.def( "__init__", @@ -226,10 +230,11 @@ struct constructor { extra...); } - template , Args...>::value, - int> = 0> + template < + typename Class, + typename... Extra, + enable_if_t, Args...>::value, int> + = 0> static void execute(Class &cl, const Extra &...extra) { cl.def( "__init__", @@ -245,10 +250,11 @@ struct constructor { // Implementing class for py::init_alias<...>() template struct alias_constructor { - template , Args...>::value, - int> = 0> + template < + typename Class, + typename... Extra, + enable_if_t, Args...>::value, int> + = 0> static void execute(Class &cl, const Extra &...extra) { cl.def( "__init__", diff --git a/pybind11/include/pybind11/detail/internals.h b/pybind11/include/pybind11/detail/internals.h index 6ca5e1482..aaa7f8686 100644 --- a/pybind11/include/pybind11/detail/internals.h +++ b/pybind11/include/pybind11/detail/internals.h @@ -9,6 +9,12 @@ #pragma once +#include "common.h" + +#if defined(WITH_THREAD) && defined(PYBIND11_SIMPLE_GIL_MANAGEMENT) +# include "../gil.h" +#endif + #include "../pytypes.h" #include @@ -28,15 +34,26 @@ /// further ABI-incompatible changes may be made before the ABI is officially /// changed to the new version. #ifndef PYBIND11_INTERNALS_VERSION -# define PYBIND11_INTERNALS_VERSION 4 +# if PY_VERSION_HEX >= 0x030C0000 +// Version bump for Python 3.12+, before first 3.12 beta release. +# define PYBIND11_INTERNALS_VERSION 5 +# else +# define PYBIND11_INTERNALS_VERSION 4 +# endif #endif +// This requirement is mainly to reduce the support burden (see PR #4570). +static_assert(PY_VERSION_HEX < 0x030C0000 || PYBIND11_INTERNALS_VERSION >= 5, + "pybind11 ABI version 5 is the minimum for Python 3.12+"); + PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE) using ExceptionTranslator = void (*)(std::exception_ptr); PYBIND11_NAMESPACE_BEGIN(detail) +constexpr const char *internals_function_record_capsule_name = "pybind11_function_record_capsule"; + // Forward declarations inline PyTypeObject *make_static_property_type(); inline PyTypeObject *make_default_metaclass(); @@ -49,7 +66,7 @@ inline PyObject *make_object_base_type(PyTypeObject *metaclass); // `Py_LIMITED_API` anyway. # if PYBIND11_INTERNALS_VERSION > 4 # define PYBIND11_TLS_KEY_REF Py_tss_t & -# ifdef __GNUC__ +# if defined(__GNUC__) && !defined(__INTEL_COMPILER) // Clang on macOS warns due to `Py_tss_NEEDS_INIT` not specifying an initializer // for every field. # define PYBIND11_TLS_KEY_INIT(var) \ @@ -106,7 +123,8 @@ inline void tls_replace_value(PYBIND11_TLS_KEY_REF key, void *value) { // libstdc++, this doesn't happen: equality and the type_index hash are based on the type name, // which works. If not under a known-good stl, provide our own name-based hash and equality // functions that use the type name. -#if defined(__GLIBCXX__) +#if (PYBIND11_INTERNALS_VERSION <= 4 && defined(__GLIBCXX__)) \ + || (PYBIND11_INTERNALS_VERSION >= 5 && !defined(_LIBCPP_VERSION)) inline bool same_type(const std::type_info &lhs, const std::type_info &rhs) { return lhs == rhs; } using type_hash = std::hash; using type_equal_to = std::equal_to; @@ -169,11 +187,23 @@ struct internals { PyTypeObject *default_metaclass; PyObject *instance_base; #if defined(WITH_THREAD) + // Unused if PYBIND11_SIMPLE_GIL_MANAGEMENT is defined: PYBIND11_TLS_KEY_INIT(tstate) # if PYBIND11_INTERNALS_VERSION > 4 PYBIND11_TLS_KEY_INIT(loader_life_support_tls_key) # endif // PYBIND11_INTERNALS_VERSION > 4 + // Unused if PYBIND11_SIMPLE_GIL_MANAGEMENT is defined: PyInterpreterState *istate = nullptr; + +# if PYBIND11_INTERNALS_VERSION > 4 + // Note that we have to use a std::string to allocate memory to ensure a unique address + // We want unique addresses since we use pointer equality to compare function records + std::string function_record_capsule_name = internals_function_record_capsule_name; +# endif + + internals() = default; + internals(const internals &other) = delete; + internals &operator=(const internals &other) = delete; ~internals() { # if PYBIND11_INTERNALS_VERSION > 4 PYBIND11_TLS_FREE(loader_life_support_tls_key); @@ -401,6 +431,38 @@ inline void translate_local_exception(std::exception_ptr p) { } #endif +inline object get_python_state_dict() { + object state_dict; +#if PYBIND11_INTERNALS_VERSION <= 4 || PY_VERSION_HEX < 0x03080000 || defined(PYPY_VERSION) + state_dict = reinterpret_borrow(PyEval_GetBuiltins()); +#else +# if PY_VERSION_HEX < 0x03090000 + PyInterpreterState *istate = _PyInterpreterState_Get(); +# else + PyInterpreterState *istate = PyInterpreterState_Get(); +# endif + if (istate) { + state_dict = reinterpret_borrow(PyInterpreterState_GetDict(istate)); + } +#endif + if (!state_dict) { + raise_from(PyExc_SystemError, "pybind11::detail::get_python_state_dict() FAILED"); + } + return state_dict; +} + +inline object get_internals_obj_from_state_dict(handle state_dict) { + return reinterpret_borrow(dict_getitemstring(state_dict.ptr(), PYBIND11_INTERNALS_ID)); +} + +inline internals **get_internals_pp_from_capsule(handle obj) { + void *raw_ptr = PyCapsule_GetPointer(obj.ptr(), /*name=*/nullptr); + if (raw_ptr == nullptr) { + raise_from(PyExc_SystemError, "pybind11::detail::get_internals_pp_from_capsule() FAILED"); + } + return static_cast(raw_ptr); +} + /// Return a reference to the current `internals` data PYBIND11_NOINLINE internals &get_internals() { auto **&internals_pp = get_internals_pp(); @@ -408,21 +470,29 @@ PYBIND11_NOINLINE internals &get_internals() { return **internals_pp; } +#if defined(WITH_THREAD) +# if defined(PYBIND11_SIMPLE_GIL_MANAGEMENT) + gil_scoped_acquire gil; +# else // Ensure that the GIL is held since we will need to make Python calls. // Cannot use py::gil_scoped_acquire here since that constructor calls get_internals. struct gil_scoped_acquire_local { gil_scoped_acquire_local() : state(PyGILState_Ensure()) {} + gil_scoped_acquire_local(const gil_scoped_acquire_local &) = delete; + gil_scoped_acquire_local &operator=(const gil_scoped_acquire_local &) = delete; ~gil_scoped_acquire_local() { PyGILState_Release(state); } const PyGILState_STATE state; } gil; +# endif +#endif error_scope err_scope; - PYBIND11_STR_TYPE id(PYBIND11_INTERNALS_ID); - auto builtins = handle(PyEval_GetBuiltins()); - if (builtins.contains(id) && isinstance(builtins[id])) { - internals_pp = static_cast(capsule(builtins[id])); - - // We loaded builtins through python's builtins, which means that our `error_already_set` + dict state_dict = get_python_state_dict(); + if (object internals_obj = get_internals_obj_from_state_dict(state_dict)) { + internals_pp = get_internals_pp_from_capsule(internals_obj); + } + if (internals_pp && *internals_pp) { + // We loaded the internals through `state_dict`, which means that our `error_already_set` // and `builtin_exception` may be different local classes than the ones set up in the // initial exception translator, below, so add another for our local exception classes. // @@ -440,16 +510,15 @@ PYBIND11_NOINLINE internals &get_internals() { internals_ptr = new internals(); #if defined(WITH_THREAD) -# if PY_VERSION_HEX < 0x03090000 - PyEval_InitThreads(); -# endif PyThreadState *tstate = PyThreadState_Get(); + // NOLINTNEXTLINE(bugprone-assignment-in-if-condition) if (!PYBIND11_TLS_KEY_CREATE(internals_ptr->tstate)) { pybind11_fail("get_internals: could not successfully initialize the tstate TSS key!"); } PYBIND11_TLS_REPLACE_VALUE(internals_ptr->tstate, tstate); # if PYBIND11_INTERNALS_VERSION > 4 + // NOLINTNEXTLINE(bugprone-assignment-in-if-condition) if (!PYBIND11_TLS_KEY_CREATE(internals_ptr->loader_life_support_tls_key)) { pybind11_fail("get_internals: could not successfully initialize the " "loader_life_support TSS key!"); @@ -457,7 +526,7 @@ PYBIND11_NOINLINE internals &get_internals() { # endif internals_ptr->istate = tstate->interp; #endif - builtins[id] = capsule(internals_pp); + state_dict[PYBIND11_INTERNALS_ID] = capsule(internals_pp); internals_ptr->registered_exception_translators.push_front(&translate_exception); internals_ptr->static_property_type = make_static_property_type(); internals_ptr->default_metaclass = make_default_metaclass(); @@ -489,6 +558,7 @@ struct local_internals { struct shared_loader_life_support_data { PYBIND11_TLS_KEY_INIT(loader_life_support_tls_key) shared_loader_life_support_data() { + // NOLINTNEXTLINE(bugprone-assignment-in-if-condition) if (!PYBIND11_TLS_KEY_CREATE(loader_life_support_tls_key)) { pybind11_fail("local_internals: could not successfully initialize the " "loader_life_support TLS key!"); @@ -512,8 +582,13 @@ struct local_internals { /// Works like `get_internals`, but for things which are locally registered. inline local_internals &get_local_internals() { - static local_internals locals; - return locals; + // Current static can be created in the interpreter finalization routine. If the later will be + // destroyed in another static variable destructor, creation of this static there will cause + // static deinitialization fiasco. In order to avoid it we avoid destruction of the + // local_internals static. One can read more about the problem and current solution here: + // https://google.github.io/styleguide/cppguide.html#Static_and_Global_Variables + static auto *locals = new local_internals(); + return *locals; } /// Constructs a std::string with the given arguments, stores it in `internals`, and returns its @@ -527,6 +602,25 @@ const char *c_str(Args &&...args) { return strings.front().c_str(); } +inline const char *get_function_record_capsule_name() { +#if PYBIND11_INTERNALS_VERSION > 4 + return get_internals().function_record_capsule_name.c_str(); +#else + return nullptr; +#endif +} + +// Determine whether or not the following capsule contains a pybind11 function record. +// Note that we use `internals` to make sure that only ABI compatible records are touched. +// +// This check is currently used in two places: +// - An important optimization in functional.h to avoid overhead in C++ -> Python -> C++ +// - The sibling feature of cpp_function to allow overloads +inline bool is_function_record_capsule(const capsule &cap) { + // Pointer equality as we rely on internals() to ensure unique pointers + return cap.name() == get_function_record_capsule_name(); +} + PYBIND11_NAMESPACE_END(detail) /// Returns a named pointer that is shared among all extension modules (using the same diff --git a/pybind11/include/pybind11/detail/type_caster_base.h b/pybind11/include/pybind11/detail/type_caster_base.h index 21f69c289..16387506c 100644 --- a/pybind11/include/pybind11/detail/type_caster_base.h +++ b/pybind11/include/pybind11/detail/type_caster_base.h @@ -258,9 +258,9 @@ struct value_and_holder { // Main constructor for a found value/holder: value_and_holder(instance *i, const detail::type_info *type, size_t vpos, size_t index) - : inst{i}, index{index}, type{type}, vh{inst->simple_layout - ? inst->simple_value_holder - : &inst->nonsimple.values_and_holders[vpos]} {} + : inst{i}, index{index}, type{type}, + vh{inst->simple_layout ? inst->simple_value_holder + : &inst->nonsimple.values_and_holders[vpos]} {} // Default constructor (used to signal a value-and-holder not found by get_value_and_holder()) value_and_holder() = default; @@ -822,23 +822,179 @@ using movable_cast_op_type typename std::add_rvalue_reference>::type, typename std::add_lvalue_reference>::type>>; +// Does the container have a mapped type and is it recursive? +// Implemented by specializations below. +template +struct container_mapped_type_traits { + static constexpr bool has_mapped_type = false; + static constexpr bool has_recursive_mapped_type = false; +}; + +template +struct container_mapped_type_traits< + Container, + typename std::enable_if< + std::is_same::value>::type> { + static constexpr bool has_mapped_type = true; + static constexpr bool has_recursive_mapped_type = true; +}; + +template +struct container_mapped_type_traits< + Container, + typename std::enable_if< + negation>::value>::type> { + static constexpr bool has_mapped_type = true; + static constexpr bool has_recursive_mapped_type = false; +}; + +// Does the container have a value type and is it recursive? +// Implemented by specializations below. +template +struct container_value_type_traits : std::false_type { + static constexpr bool has_value_type = false; + static constexpr bool has_recursive_value_type = false; +}; + +template +struct container_value_type_traits< + Container, + typename std::enable_if< + std::is_same::value>::type> { + static constexpr bool has_value_type = true; + static constexpr bool has_recursive_value_type = true; +}; + +template +struct container_value_type_traits< + Container, + typename std::enable_if< + negation>::value>::type> { + static constexpr bool has_value_type = true; + static constexpr bool has_recursive_value_type = false; +}; + +/* + * Tag to be used for representing the bottom of recursively defined types. + * Define this tag so we don't have to use void. + */ +struct recursive_bottom {}; + +/* + * Implementation detail of `recursive_container_traits` below. + * `T` is the `value_type` of the container, which might need to be modified to + * avoid recursive types and const types. + */ +template +struct impl_type_to_check_recursively { + /* + * If the container is recursive, then no further recursion should be done. + */ + using if_recursive = recursive_bottom; + /* + * Otherwise yield `T` unchanged. + */ + using if_not_recursive = T; +}; + +/* + * For pairs - only as value type of a map -, the first type should remove the `const`. + * Also, if the map is recursive, then the recursive checking should consider + * the first type only. + */ +template +struct impl_type_to_check_recursively, /* is_this_a_map = */ true> { + using if_recursive = typename std::remove_const::type; + using if_not_recursive = std::pair::type, B>; +}; + +/* + * Implementation of `recursive_container_traits` below. + */ +template +struct impl_recursive_container_traits { + using type_to_check_recursively = recursive_bottom; +}; + +template +struct impl_recursive_container_traits< + Container, + typename std::enable_if::has_value_type>::type> { + static constexpr bool is_recursive + = container_mapped_type_traits::has_recursive_mapped_type + || container_value_type_traits::has_recursive_value_type; + /* + * This member dictates which type Pybind11 should check recursively in traits + * such as `is_move_constructible`, `is_copy_constructible`, `is_move_assignable`, ... + * Direct access to `value_type` should be avoided: + * 1. `value_type` might recursively contain the type again + * 2. `value_type` of STL map types is `std::pair`, the `const` + * should be removed. + * + */ + using type_to_check_recursively = typename std::conditional< + is_recursive, + typename impl_type_to_check_recursively< + typename Container::value_type, + container_mapped_type_traits::has_mapped_type>::if_recursive, + typename impl_type_to_check_recursively< + typename Container::value_type, + container_mapped_type_traits::has_mapped_type>::if_not_recursive>::type; +}; + +/* + * This trait defines the `type_to_check_recursively` which is needed to properly + * handle recursively defined traits such as `is_move_constructible` without going + * into an infinite recursion. + * Should be used instead of directly accessing the `value_type`. + * It cancels the recursion by returning the `recursive_bottom` tag. + * + * The default definition of `type_to_check_recursively` is as follows: + * + * 1. By default, it is `recursive_bottom`, so that the recursion is canceled. + * 2. If the type is non-recursive and defines a `value_type`, then the `value_type` is used. + * If the `value_type` is a pair and a `mapped_type` is defined, + * then the `const` is removed from the first type. + * 3. If the type is recursive and `value_type` is not a pair, then `recursive_bottom` is returned. + * 4. If the type is recursive and `value_type` is a pair and a `mapped_type` is defined, + * then `const` is removed from the first type and the first type is returned. + * + * This behavior can be extended by the user as seen in test_stl_binders.cpp. + * + * This struct is exactly the same as impl_recursive_container_traits. + * The duplication achieves that user-defined specializations don't compete + * with internal specializations, but take precedence. + */ +template +struct recursive_container_traits : impl_recursive_container_traits {}; + +template +struct is_move_constructible + : all_of, + is_move_constructible< + typename recursive_container_traits::type_to_check_recursively>> {}; + +template <> +struct is_move_constructible : std::true_type {}; + +// Likewise for std::pair +// (after C++17 it is mandatory that the move constructor not exist when the two types aren't +// themselves move constructible, but this can not be relied upon when T1 or T2 are themselves +// containers). +template +struct is_move_constructible> + : all_of, is_move_constructible> {}; + // std::is_copy_constructible isn't quite enough: it lets std::vector (and similar) through when // T is non-copyable, but code containing such a copy constructor fails to actually compile. -template -struct is_copy_constructible : std::is_copy_constructible {}; +template +struct is_copy_constructible + : all_of, + is_copy_constructible< + typename recursive_container_traits::type_to_check_recursively>> {}; -// Specialization for types that appear to be copy constructible but also look like stl containers -// (we specifically check for: has `value_type` and `reference` with `reference = value_type&`): if -// so, copy constructability depends on whether the value_type is copy constructible. -template -struct is_copy_constructible< - Container, - enable_if_t< - all_of, - std::is_same, - // Avoid infinite recursion - negation>>::value>> - : is_copy_constructible {}; +template <> +struct is_copy_constructible : std::true_type {}; // Likewise for std::pair // (after C++17 it is mandatory that the copy constructor not exist when the two types aren't @@ -849,14 +1005,16 @@ struct is_copy_constructible> : all_of, is_copy_constructible> {}; // The same problems arise with std::is_copy_assignable, so we use the same workaround. -template -struct is_copy_assignable : std::is_copy_assignable {}; -template -struct is_copy_assignable, - std::is_same>::value>> - : is_copy_assignable {}; +template +struct is_copy_assignable + : all_of< + std::is_copy_assignable, + is_copy_assignable::type_to_check_recursively>> { +}; + +template <> +struct is_copy_assignable : std::true_type {}; + template struct is_copy_assignable> : all_of, is_copy_assignable> {}; @@ -994,7 +1152,7 @@ protected: return [](const void *arg) -> void * { return new T(*reinterpret_cast(arg)); }; } - template ::value>> + template ::value>> static auto make_move_constructor(const T *) -> decltype(new T(std::declval()), Constructor{}) { return [](const void *arg) -> void * { @@ -1006,5 +1164,14 @@ protected: static Constructor make_move_constructor(...) { return nullptr; } }; +PYBIND11_NOINLINE std::string type_info_description(const std::type_info &ti) { + if (auto *type_data = get_type_info(ti)) { + handle th((PyObject *) type_data->type); + return th.attr("__module__").cast() + '.' + + th.attr("__qualname__").cast(); + } + return clean_type_id(ti.name()); +} + PYBIND11_NAMESPACE_END(detail) PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE) diff --git a/pybind11/include/pybind11/eigen.h b/pybind11/include/pybind11/eigen.h index 4d221b3d7..273b9c930 100644 --- a/pybind11/include/pybind11/eigen.h +++ b/pybind11/include/pybind11/eigen.h @@ -9,700 +9,4 @@ #pragma once -/* HINT: To suppress warnings originating from the Eigen headers, use -isystem. - See also: - https://stackoverflow.com/questions/2579576/i-dir-vs-isystem-dir - https://stackoverflow.com/questions/1741816/isystem-for-ms-visual-studio-c-compiler -*/ - -#include "numpy.h" - -// The C4127 suppression was introduced for Eigen 3.4.0. In theory we could -// make it version specific, or even remove it later, but considering that -// 1. C4127 is generally far more distracting than useful for modern template code, and -// 2. we definitely want to ignore any MSVC warnings originating from Eigen code, -// it is probably best to keep this around indefinitely. -#if defined(_MSC_VER) -# pragma warning(push) -# pragma warning(disable : 4127) // C4127: conditional expression is constant -# pragma warning(disable : 5054) // https://github.com/pybind/pybind11/pull/3741 -// C5054: operator '&': deprecated between enumerations of different types -#endif - -#include -#include - -#if defined(_MSC_VER) -# pragma warning(pop) -#endif - -// Eigen prior to 3.2.7 doesn't have proper move constructors--but worse, some classes get implicit -// move constructors that break things. We could detect this an explicitly copy, but an extra copy -// of matrices seems highly undesirable. -static_assert(EIGEN_VERSION_AT_LEAST(3, 2, 7), - "Eigen support in pybind11 requires Eigen >= 3.2.7"); - -PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE) - -// Provide a convenience alias for easier pass-by-ref usage with fully dynamic strides: -using EigenDStride = Eigen::Stride; -template -using EigenDRef = Eigen::Ref; -template -using EigenDMap = Eigen::Map; - -PYBIND11_NAMESPACE_BEGIN(detail) - -#if EIGEN_VERSION_AT_LEAST(3, 3, 0) -using EigenIndex = Eigen::Index; -template -using EigenMapSparseMatrix = Eigen::Map>; -#else -using EigenIndex = EIGEN_DEFAULT_DENSE_INDEX_TYPE; -template -using EigenMapSparseMatrix = Eigen::MappedSparseMatrix; -#endif - -// Matches Eigen::Map, Eigen::Ref, blocks, etc: -template -using is_eigen_dense_map = all_of, - std::is_base_of, T>>; -template -using is_eigen_mutable_map = std::is_base_of, T>; -template -using is_eigen_dense_plain - = all_of>, is_template_base_of>; -template -using is_eigen_sparse = is_template_base_of; -// Test for objects inheriting from EigenBase that aren't captured by the above. This -// basically covers anything that can be assigned to a dense matrix but that don't have a typical -// matrix data layout that can be copied from their .data(). For example, DiagonalMatrix and -// SelfAdjointView fall into this category. -template -using is_eigen_other - = all_of, - negation, is_eigen_dense_plain, is_eigen_sparse>>>; - -// Captures numpy/eigen conformability status (returned by EigenProps::conformable()): -template -struct EigenConformable { - bool conformable = false; - EigenIndex rows = 0, cols = 0; - EigenDStride stride{0, 0}; // Only valid if negativestrides is false! - bool negativestrides = false; // If true, do not use stride! - - // NOLINTNEXTLINE(google-explicit-constructor) - EigenConformable(bool fits = false) : conformable{fits} {} - // Matrix type: - EigenConformable(EigenIndex r, EigenIndex c, EigenIndex rstride, EigenIndex cstride) - : conformable{true}, rows{r}, cols{c}, - // TODO: when Eigen bug #747 is fixed, remove the tests for non-negativity. - // http://eigen.tuxfamily.org/bz/show_bug.cgi?id=747 - stride{EigenRowMajor ? (rstride > 0 ? rstride : 0) - : (cstride > 0 ? cstride : 0) /* outer stride */, - EigenRowMajor ? (cstride > 0 ? cstride : 0) - : (rstride > 0 ? rstride : 0) /* inner stride */}, - negativestrides{rstride < 0 || cstride < 0} {} - // Vector type: - EigenConformable(EigenIndex r, EigenIndex c, EigenIndex stride) - : EigenConformable(r, c, r == 1 ? c * stride : stride, c == 1 ? r : r * stride) {} - - template - bool stride_compatible() const { - // To have compatible strides, we need (on both dimensions) one of fully dynamic strides, - // matching strides, or a dimension size of 1 (in which case the stride value is - // irrelevant). Alternatively, if any dimension size is 0, the strides are not relevant - // (and numpy ≥ 1.23 sets the strides to 0 in that case, so we need to check explicitly). - if (negativestrides) { - return false; - } - if (rows == 0 || cols == 0) { - return true; - } - return (props::inner_stride == Eigen::Dynamic || props::inner_stride == stride.inner() - || (EigenRowMajor ? cols : rows) == 1) - && (props::outer_stride == Eigen::Dynamic || props::outer_stride == stride.outer() - || (EigenRowMajor ? rows : cols) == 1); - } - // NOLINTNEXTLINE(google-explicit-constructor) - operator bool() const { return conformable; } -}; - -template -struct eigen_extract_stride { - using type = Type; -}; -template -struct eigen_extract_stride> { - using type = StrideType; -}; -template -struct eigen_extract_stride> { - using type = StrideType; -}; - -// Helper struct for extracting information from an Eigen type -template -struct EigenProps { - using Type = Type_; - using Scalar = typename Type::Scalar; - using StrideType = typename eigen_extract_stride::type; - static constexpr EigenIndex rows = Type::RowsAtCompileTime, cols = Type::ColsAtCompileTime, - size = Type::SizeAtCompileTime; - static constexpr bool row_major = Type::IsRowMajor, - vector - = Type::IsVectorAtCompileTime, // At least one dimension has fixed size 1 - fixed_rows = rows != Eigen::Dynamic, fixed_cols = cols != Eigen::Dynamic, - fixed = size != Eigen::Dynamic, // Fully-fixed size - dynamic = !fixed_rows && !fixed_cols; // Fully-dynamic size - - template - using if_zero = std::integral_constant; - static constexpr EigenIndex inner_stride - = if_zero::value, - outer_stride = if_zero < StrideType::OuterStrideAtCompileTime, - vector ? size - : row_major ? cols - : rows > ::value; - static constexpr bool dynamic_stride - = inner_stride == Eigen::Dynamic && outer_stride == Eigen::Dynamic; - static constexpr bool requires_row_major - = !dynamic_stride && !vector && (row_major ? inner_stride : outer_stride) == 1; - static constexpr bool requires_col_major - = !dynamic_stride && !vector && (row_major ? outer_stride : inner_stride) == 1; - - // Takes an input array and determines whether we can make it fit into the Eigen type. If - // the array is a vector, we attempt to fit it into either an Eigen 1xN or Nx1 vector - // (preferring the latter if it will fit in either, i.e. for a fully dynamic matrix type). - static EigenConformable conformable(const array &a) { - const auto dims = a.ndim(); - if (dims < 1 || dims > 2) { - return false; - } - - if (dims == 2) { // Matrix type: require exact match (or dynamic) - - EigenIndex np_rows = a.shape(0), np_cols = a.shape(1), - np_rstride = a.strides(0) / static_cast(sizeof(Scalar)), - np_cstride = a.strides(1) / static_cast(sizeof(Scalar)); - if ((PYBIND11_SILENCE_MSVC_C4127(fixed_rows) && np_rows != rows) - || (PYBIND11_SILENCE_MSVC_C4127(fixed_cols) && np_cols != cols)) { - return false; - } - - return {np_rows, np_cols, np_rstride, np_cstride}; - } - - // Otherwise we're storing an n-vector. Only one of the strides will be used, but - // whichever is used, we want the (single) numpy stride value. - const EigenIndex n = a.shape(0), - stride = a.strides(0) / static_cast(sizeof(Scalar)); - - if (vector) { // Eigen type is a compile-time vector - if (PYBIND11_SILENCE_MSVC_C4127(fixed) && size != n) { - return false; // Vector size mismatch - } - return {rows == 1 ? 1 : n, cols == 1 ? 1 : n, stride}; - } - if (fixed) { - // The type has a fixed size, but is not a vector: abort - return false; - } - if (fixed_cols) { - // Since this isn't a vector, cols must be != 1. We allow this only if it exactly - // equals the number of elements (rows is Dynamic, and so 1 row is allowed). - if (cols != n) { - return false; - } - return {1, n, stride}; - } // Otherwise it's either fully dynamic, or column dynamic; both become a column vector - if (PYBIND11_SILENCE_MSVC_C4127(fixed_rows) && rows != n) { - return false; - } - return {n, 1, stride}; - } - - static constexpr bool show_writeable - = is_eigen_dense_map::value && is_eigen_mutable_map::value; - static constexpr bool show_order = is_eigen_dense_map::value; - static constexpr bool show_c_contiguous = show_order && requires_row_major; - static constexpr bool show_f_contiguous - = !show_c_contiguous && show_order && requires_col_major; - - static constexpr auto descriptor - = const_name("numpy.ndarray[") + npy_format_descriptor::name + const_name("[") - + const_name(const_name<(size_t) rows>(), const_name("m")) + const_name(", ") - + const_name(const_name<(size_t) cols>(), const_name("n")) + const_name("]") - + - // For a reference type (e.g. Ref) we have other constraints that might need to - // be satisfied: writeable=True (for a mutable reference), and, depending on the map's - // stride options, possibly f_contiguous or c_contiguous. We include them in the - // descriptor output to provide some hint as to why a TypeError is occurring (otherwise - // it can be confusing to see that a function accepts a 'numpy.ndarray[float64[3,2]]' and - // an error message that you *gave* a numpy.ndarray of the right type and dimensions. - const_name(", flags.writeable", "") - + const_name(", flags.c_contiguous", "") - + const_name(", flags.f_contiguous", "") + const_name("]"); -}; - -// Casts an Eigen type to numpy array. If given a base, the numpy array references the src data, -// otherwise it'll make a copy. writeable lets you turn off the writeable flag for the array. -template -handle -eigen_array_cast(typename props::Type const &src, handle base = handle(), bool writeable = true) { - constexpr ssize_t elem_size = sizeof(typename props::Scalar); - array a; - if (props::vector) { - a = array({src.size()}, {elem_size * src.innerStride()}, src.data(), base); - } else { - a = array({src.rows(), src.cols()}, - {elem_size * src.rowStride(), elem_size * src.colStride()}, - src.data(), - base); - } - - if (!writeable) { - array_proxy(a.ptr())->flags &= ~detail::npy_api::NPY_ARRAY_WRITEABLE_; - } - - return a.release(); -} - -// Takes an lvalue ref to some Eigen type and a (python) base object, creating a numpy array that -// reference the Eigen object's data with `base` as the python-registered base class (if omitted, -// the base will be set to None, and lifetime management is up to the caller). The numpy array is -// non-writeable if the given type is const. -template -handle eigen_ref_array(Type &src, handle parent = none()) { - // none here is to get past array's should-we-copy detection, which currently always - // copies when there is no base. Setting the base to None should be harmless. - return eigen_array_cast(src, parent, !std::is_const::value); -} - -// Takes a pointer to some dense, plain Eigen type, builds a capsule around it, then returns a -// numpy array that references the encapsulated data with a python-side reference to the capsule to -// tie its destruction to that of any dependent python objects. Const-ness is determined by -// whether or not the Type of the pointer given is const. -template ::value>> -handle eigen_encapsulate(Type *src) { - capsule base(src, [](void *o) { delete static_cast(o); }); - return eigen_ref_array(*src, base); -} - -// Type caster for regular, dense matrix types (e.g. MatrixXd), but not maps/refs/etc. of dense -// types. -template -struct type_caster::value>> { - using Scalar = typename Type::Scalar; - using props = EigenProps; - - bool load(handle src, bool convert) { - // If we're in no-convert mode, only load if given an array of the correct type - if (!convert && !isinstance>(src)) { - return false; - } - - // Coerce into an array, but don't do type conversion yet; the copy below handles it. - auto buf = array::ensure(src); - - if (!buf) { - return false; - } - - auto dims = buf.ndim(); - if (dims < 1 || dims > 2) { - return false; - } - - auto fits = props::conformable(buf); - if (!fits) { - return false; - } - - // Allocate the new type, then build a numpy reference into it - value = Type(fits.rows, fits.cols); - auto ref = reinterpret_steal(eigen_ref_array(value)); - if (dims == 1) { - ref = ref.squeeze(); - } else if (ref.ndim() == 1) { - buf = buf.squeeze(); - } - - int result = detail::npy_api::get().PyArray_CopyInto_(ref.ptr(), buf.ptr()); - - if (result < 0) { // Copy failed! - PyErr_Clear(); - return false; - } - - return true; - } - -private: - // Cast implementation - template - static handle cast_impl(CType *src, return_value_policy policy, handle parent) { - switch (policy) { - case return_value_policy::take_ownership: - case return_value_policy::automatic: - return eigen_encapsulate(src); - case return_value_policy::move: - return eigen_encapsulate(new CType(std::move(*src))); - case return_value_policy::copy: - return eigen_array_cast(*src); - case return_value_policy::reference: - case return_value_policy::automatic_reference: - return eigen_ref_array(*src); - case return_value_policy::reference_internal: - return eigen_ref_array(*src, parent); - default: - throw cast_error("unhandled return_value_policy: should not happen!"); - }; - } - -public: - // Normal returned non-reference, non-const value: - static handle cast(Type &&src, return_value_policy /* policy */, handle parent) { - return cast_impl(&src, return_value_policy::move, parent); - } - // If you return a non-reference const, we mark the numpy array readonly: - static handle cast(const Type &&src, return_value_policy /* policy */, handle parent) { - return cast_impl(&src, return_value_policy::move, parent); - } - // lvalue reference return; default (automatic) becomes copy - static handle cast(Type &src, return_value_policy policy, handle parent) { - if (policy == return_value_policy::automatic - || policy == return_value_policy::automatic_reference) { - policy = return_value_policy::copy; - } - return cast_impl(&src, policy, parent); - } - // const lvalue reference return; default (automatic) becomes copy - static handle cast(const Type &src, return_value_policy policy, handle parent) { - if (policy == return_value_policy::automatic - || policy == return_value_policy::automatic_reference) { - policy = return_value_policy::copy; - } - return cast(&src, policy, parent); - } - // non-const pointer return - static handle cast(Type *src, return_value_policy policy, handle parent) { - return cast_impl(src, policy, parent); - } - // const pointer return - static handle cast(const Type *src, return_value_policy policy, handle parent) { - return cast_impl(src, policy, parent); - } - - static constexpr auto name = props::descriptor; - - // NOLINTNEXTLINE(google-explicit-constructor) - operator Type *() { return &value; } - // NOLINTNEXTLINE(google-explicit-constructor) - operator Type &() { return value; } - // NOLINTNEXTLINE(google-explicit-constructor) - operator Type &&() && { return std::move(value); } - template - using cast_op_type = movable_cast_op_type; - -private: - Type value; -}; - -// Base class for casting reference/map/block/etc. objects back to python. -template -struct eigen_map_caster { -private: - using props = EigenProps; - -public: - // Directly referencing a ref/map's data is a bit dangerous (whatever the map/ref points to has - // to stay around), but we'll allow it under the assumption that you know what you're doing - // (and have an appropriate keep_alive in place). We return a numpy array pointing directly at - // the ref's data (The numpy array ends up read-only if the ref was to a const matrix type.) - // Note that this means you need to ensure you don't destroy the object in some other way (e.g. - // with an appropriate keep_alive, or with a reference to a statically allocated matrix). - static handle cast(const MapType &src, return_value_policy policy, handle parent) { - switch (policy) { - case return_value_policy::copy: - return eigen_array_cast(src); - case return_value_policy::reference_internal: - return eigen_array_cast(src, parent, is_eigen_mutable_map::value); - case return_value_policy::reference: - case return_value_policy::automatic: - case return_value_policy::automatic_reference: - return eigen_array_cast(src, none(), is_eigen_mutable_map::value); - default: - // move, take_ownership don't make any sense for a ref/map: - pybind11_fail("Invalid return_value_policy for Eigen Map/Ref/Block type"); - } - } - - static constexpr auto name = props::descriptor; - - // Explicitly delete these: support python -> C++ conversion on these (i.e. these can be return - // types but not bound arguments). We still provide them (with an explicitly delete) so that - // you end up here if you try anyway. - bool load(handle, bool) = delete; - operator MapType() = delete; - template - using cast_op_type = MapType; -}; - -// We can return any map-like object (but can only load Refs, specialized next): -template -struct type_caster::value>> : eigen_map_caster {}; - -// Loader for Ref<...> arguments. See the documentation for info on how to make this work without -// copying (it requires some extra effort in many cases). -template -struct type_caster< - Eigen::Ref, - enable_if_t>::value>> - : public eigen_map_caster> { -private: - using Type = Eigen::Ref; - using props = EigenProps; - using Scalar = typename props::Scalar; - using MapType = Eigen::Map; - using Array - = array_t; - static constexpr bool need_writeable = is_eigen_mutable_map::value; - // Delay construction (these have no default constructor) - std::unique_ptr map; - std::unique_ptr ref; - // Our array. When possible, this is just a numpy array pointing to the source data, but - // sometimes we can't avoid copying (e.g. input is not a numpy array at all, has an - // incompatible layout, or is an array of a type that needs to be converted). Using a numpy - // temporary (rather than an Eigen temporary) saves an extra copy when we need both type - // conversion and storage order conversion. (Note that we refuse to use this temporary copy - // when loading an argument for a Ref with M non-const, i.e. a read-write reference). - Array copy_or_ref; - -public: - bool load(handle src, bool convert) { - // First check whether what we have is already an array of the right type. If not, we - // can't avoid a copy (because the copy is also going to do type conversion). - bool need_copy = !isinstance(src); - - EigenConformable fits; - if (!need_copy) { - // We don't need a converting copy, but we also need to check whether the strides are - // compatible with the Ref's stride requirements - auto aref = reinterpret_borrow(src); - - if (aref && (!need_writeable || aref.writeable())) { - fits = props::conformable(aref); - if (!fits) { - return false; // Incompatible dimensions - } - if (!fits.template stride_compatible()) { - need_copy = true; - } else { - copy_or_ref = std::move(aref); - } - } else { - need_copy = true; - } - } - - if (need_copy) { - // We need to copy: If we need a mutable reference, or we're not supposed to convert - // (either because we're in the no-convert overload pass, or because we're explicitly - // instructed not to copy (via `py::arg().noconvert()`) we have to fail loading. - if (!convert || need_writeable) { - return false; - } - - Array copy = Array::ensure(src); - if (!copy) { - return false; - } - fits = props::conformable(copy); - if (!fits || !fits.template stride_compatible()) { - return false; - } - copy_or_ref = std::move(copy); - loader_life_support::add_patient(copy_or_ref); - } - - ref.reset(); - map.reset(new MapType(data(copy_or_ref), - fits.rows, - fits.cols, - make_stride(fits.stride.outer(), fits.stride.inner()))); - ref.reset(new Type(*map)); - - return true; - } - - // NOLINTNEXTLINE(google-explicit-constructor) - operator Type *() { return ref.get(); } - // NOLINTNEXTLINE(google-explicit-constructor) - operator Type &() { return *ref; } - template - using cast_op_type = pybind11::detail::cast_op_type<_T>; - -private: - template ::value, int> = 0> - Scalar *data(Array &a) { - return a.mutable_data(); - } - - template ::value, int> = 0> - const Scalar *data(Array &a) { - return a.data(); - } - - // Attempt to figure out a constructor of `Stride` that will work. - // If both strides are fixed, use a default constructor: - template - using stride_ctor_default = bool_constant::value>; - // Otherwise, if there is a two-index constructor, assume it is (outer,inner) like - // Eigen::Stride, and use it: - template - using stride_ctor_dual - = bool_constant::value - && std::is_constructible::value>; - // Otherwise, if there is a one-index constructor, and just one of the strides is dynamic, use - // it (passing whichever stride is dynamic). - template - using stride_ctor_outer - = bool_constant, stride_ctor_dual>::value - && S::OuterStrideAtCompileTime == Eigen::Dynamic - && S::InnerStrideAtCompileTime != Eigen::Dynamic - && std::is_constructible::value>; - template - using stride_ctor_inner - = bool_constant, stride_ctor_dual>::value - && S::InnerStrideAtCompileTime == Eigen::Dynamic - && S::OuterStrideAtCompileTime != Eigen::Dynamic - && std::is_constructible::value>; - - template ::value, int> = 0> - static S make_stride(EigenIndex, EigenIndex) { - return S(); - } - template ::value, int> = 0> - static S make_stride(EigenIndex outer, EigenIndex inner) { - return S(outer, inner); - } - template ::value, int> = 0> - static S make_stride(EigenIndex outer, EigenIndex) { - return S(outer); - } - template ::value, int> = 0> - static S make_stride(EigenIndex, EigenIndex inner) { - return S(inner); - } -}; - -// type_caster for special matrix types (e.g. DiagonalMatrix), which are EigenBase, but not -// EigenDense (i.e. they don't have a data(), at least not with the usual matrix layout). -// load() is not supported, but we can cast them into the python domain by first copying to a -// regular Eigen::Matrix, then casting that. -template -struct type_caster::value>> { -protected: - using Matrix - = Eigen::Matrix; - using props = EigenProps; - -public: - static handle cast(const Type &src, return_value_policy /* policy */, handle /* parent */) { - handle h = eigen_encapsulate(new Matrix(src)); - return h; - } - static handle cast(const Type *src, return_value_policy policy, handle parent) { - return cast(*src, policy, parent); - } - - static constexpr auto name = props::descriptor; - - // Explicitly delete these: support python -> C++ conversion on these (i.e. these can be return - // types but not bound arguments). We still provide them (with an explicitly delete) so that - // you end up here if you try anyway. - bool load(handle, bool) = delete; - operator Type() = delete; - template - using cast_op_type = Type; -}; - -template -struct type_caster::value>> { - using Scalar = typename Type::Scalar; - using StorageIndex = remove_reference_t().outerIndexPtr())>; - using Index = typename Type::Index; - static constexpr bool rowMajor = Type::IsRowMajor; - - bool load(handle src, bool) { - if (!src) { - return false; - } - - auto obj = reinterpret_borrow(src); - object sparse_module = module_::import("scipy.sparse"); - object matrix_type = sparse_module.attr(rowMajor ? "csr_matrix" : "csc_matrix"); - - if (!type::handle_of(obj).is(matrix_type)) { - try { - obj = matrix_type(obj); - } catch (const error_already_set &) { - return false; - } - } - - auto values = array_t((object) obj.attr("data")); - auto innerIndices = array_t((object) obj.attr("indices")); - auto outerIndices = array_t((object) obj.attr("indptr")); - auto shape = pybind11::tuple((pybind11::object) obj.attr("shape")); - auto nnz = obj.attr("nnz").cast(); - - if (!values || !innerIndices || !outerIndices) { - return false; - } - - value = EigenMapSparseMatrix(shape[0].cast(), - shape[1].cast(), - std::move(nnz), - outerIndices.mutable_data(), - innerIndices.mutable_data(), - values.mutable_data()); - - return true; - } - - static handle cast(const Type &src, return_value_policy /* policy */, handle /* parent */) { - const_cast(src).makeCompressed(); - - object matrix_type - = module_::import("scipy.sparse").attr(rowMajor ? "csr_matrix" : "csc_matrix"); - - array data(src.nonZeros(), src.valuePtr()); - array outerIndices((rowMajor ? src.rows() : src.cols()) + 1, src.outerIndexPtr()); - array innerIndices(src.nonZeros(), src.innerIndexPtr()); - - return matrix_type(pybind11::make_tuple( - std::move(data), std::move(innerIndices), std::move(outerIndices)), - pybind11::make_tuple(src.rows(), src.cols())) - .release(); - } - - PYBIND11_TYPE_CASTER(Type, - const_name<(Type::IsRowMajor) != 0>("scipy.sparse.csr_matrix[", - "scipy.sparse.csc_matrix[") - + npy_format_descriptor::name + const_name("]")); -}; - -PYBIND11_NAMESPACE_END(detail) -PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE) +#include "eigen/matrix.h" diff --git a/pybind11/include/pybind11/eigen/common.h b/pybind11/include/pybind11/eigen/common.h new file mode 100644 index 000000000..24f56d158 --- /dev/null +++ b/pybind11/include/pybind11/eigen/common.h @@ -0,0 +1,9 @@ +// Copyright (c) 2023 The pybind Community. + +#pragma once + +// Common message for `static_assert()`s, which are useful to easily +// preempt much less obvious errors. +#define PYBIND11_EIGEN_MESSAGE_POINTER_TYPES_ARE_NOT_SUPPORTED \ + "Pointer types (in particular `PyObject *`) are not supported as scalar types for Eigen " \ + "types." diff --git a/pybind11/include/pybind11/eigen/matrix.h b/pybind11/include/pybind11/eigen/matrix.h new file mode 100644 index 000000000..8d4342f81 --- /dev/null +++ b/pybind11/include/pybind11/eigen/matrix.h @@ -0,0 +1,714 @@ +/* + pybind11/eigen/matrix.h: Transparent conversion for dense and sparse Eigen matrices + + Copyright (c) 2016 Wenzel Jakob + + All rights reserved. Use of this source code is governed by a + BSD-style license that can be found in the LICENSE file. +*/ + +#pragma once + +#include "../numpy.h" +#include "common.h" + +/* HINT: To suppress warnings originating from the Eigen headers, use -isystem. + See also: + https://stackoverflow.com/questions/2579576/i-dir-vs-isystem-dir + https://stackoverflow.com/questions/1741816/isystem-for-ms-visual-studio-c-compiler +*/ +PYBIND11_WARNING_PUSH +PYBIND11_WARNING_DISABLE_MSVC(5054) // https://github.com/pybind/pybind11/pull/3741 +// C5054: operator '&': deprecated between enumerations of different types +#if defined(__MINGW32__) +PYBIND11_WARNING_DISABLE_GCC("-Wmaybe-uninitialized") +#endif + +#include +#include + +PYBIND11_WARNING_POP + +// Eigen prior to 3.2.7 doesn't have proper move constructors--but worse, some classes get implicit +// move constructors that break things. We could detect this an explicitly copy, but an extra copy +// of matrices seems highly undesirable. +static_assert(EIGEN_VERSION_AT_LEAST(3, 2, 7), + "Eigen matrix support in pybind11 requires Eigen >= 3.2.7"); + +PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE) + +PYBIND11_WARNING_DISABLE_MSVC(4127) + +// Provide a convenience alias for easier pass-by-ref usage with fully dynamic strides: +using EigenDStride = Eigen::Stride; +template +using EigenDRef = Eigen::Ref; +template +using EigenDMap = Eigen::Map; + +PYBIND11_NAMESPACE_BEGIN(detail) + +#if EIGEN_VERSION_AT_LEAST(3, 3, 0) +using EigenIndex = Eigen::Index; +template +using EigenMapSparseMatrix = Eigen::Map>; +#else +using EigenIndex = EIGEN_DEFAULT_DENSE_INDEX_TYPE; +template +using EigenMapSparseMatrix = Eigen::MappedSparseMatrix; +#endif + +// Matches Eigen::Map, Eigen::Ref, blocks, etc: +template +using is_eigen_dense_map = all_of, + std::is_base_of, T>>; +template +using is_eigen_mutable_map = std::is_base_of, T>; +template +using is_eigen_dense_plain + = all_of>, is_template_base_of>; +template +using is_eigen_sparse = is_template_base_of; +// Test for objects inheriting from EigenBase that aren't captured by the above. This +// basically covers anything that can be assigned to a dense matrix but that don't have a typical +// matrix data layout that can be copied from their .data(). For example, DiagonalMatrix and +// SelfAdjointView fall into this category. +template +using is_eigen_other + = all_of, + negation, is_eigen_dense_plain, is_eigen_sparse>>>; + +// Captures numpy/eigen conformability status (returned by EigenProps::conformable()): +template +struct EigenConformable { + bool conformable = false; + EigenIndex rows = 0, cols = 0; + EigenDStride stride{0, 0}; // Only valid if negativestrides is false! + bool negativestrides = false; // If true, do not use stride! + + // NOLINTNEXTLINE(google-explicit-constructor) + EigenConformable(bool fits = false) : conformable{fits} {} + // Matrix type: + EigenConformable(EigenIndex r, EigenIndex c, EigenIndex rstride, EigenIndex cstride) + : conformable{true}, rows{r}, cols{c}, + // TODO: when Eigen bug #747 is fixed, remove the tests for non-negativity. + // http://eigen.tuxfamily.org/bz/show_bug.cgi?id=747 + stride{EigenRowMajor ? (rstride > 0 ? rstride : 0) + : (cstride > 0 ? cstride : 0) /* outer stride */, + EigenRowMajor ? (cstride > 0 ? cstride : 0) + : (rstride > 0 ? rstride : 0) /* inner stride */}, + negativestrides{rstride < 0 || cstride < 0} {} + // Vector type: + EigenConformable(EigenIndex r, EigenIndex c, EigenIndex stride) + : EigenConformable(r, c, r == 1 ? c * stride : stride, c == 1 ? r : r * stride) {} + + template + bool stride_compatible() const { + // To have compatible strides, we need (on both dimensions) one of fully dynamic strides, + // matching strides, or a dimension size of 1 (in which case the stride value is + // irrelevant). Alternatively, if any dimension size is 0, the strides are not relevant + // (and numpy ≥ 1.23 sets the strides to 0 in that case, so we need to check explicitly). + if (negativestrides) { + return false; + } + if (rows == 0 || cols == 0) { + return true; + } + return (props::inner_stride == Eigen::Dynamic || props::inner_stride == stride.inner() + || (EigenRowMajor ? cols : rows) == 1) + && (props::outer_stride == Eigen::Dynamic || props::outer_stride == stride.outer() + || (EigenRowMajor ? rows : cols) == 1); + } + // NOLINTNEXTLINE(google-explicit-constructor) + operator bool() const { return conformable; } +}; + +template +struct eigen_extract_stride { + using type = Type; +}; +template +struct eigen_extract_stride> { + using type = StrideType; +}; +template +struct eigen_extract_stride> { + using type = StrideType; +}; + +// Helper struct for extracting information from an Eigen type +template +struct EigenProps { + using Type = Type_; + using Scalar = typename Type::Scalar; + using StrideType = typename eigen_extract_stride::type; + static constexpr EigenIndex rows = Type::RowsAtCompileTime, cols = Type::ColsAtCompileTime, + size = Type::SizeAtCompileTime; + static constexpr bool row_major = Type::IsRowMajor, + vector + = Type::IsVectorAtCompileTime, // At least one dimension has fixed size 1 + fixed_rows = rows != Eigen::Dynamic, fixed_cols = cols != Eigen::Dynamic, + fixed = size != Eigen::Dynamic, // Fully-fixed size + dynamic = !fixed_rows && !fixed_cols; // Fully-dynamic size + + template + using if_zero = std::integral_constant; + static constexpr EigenIndex inner_stride + = if_zero::value, + outer_stride = if_zero < StrideType::OuterStrideAtCompileTime, + vector ? size + : row_major ? cols + : rows > ::value; + static constexpr bool dynamic_stride + = inner_stride == Eigen::Dynamic && outer_stride == Eigen::Dynamic; + static constexpr bool requires_row_major + = !dynamic_stride && !vector && (row_major ? inner_stride : outer_stride) == 1; + static constexpr bool requires_col_major + = !dynamic_stride && !vector && (row_major ? outer_stride : inner_stride) == 1; + + // Takes an input array and determines whether we can make it fit into the Eigen type. If + // the array is a vector, we attempt to fit it into either an Eigen 1xN or Nx1 vector + // (preferring the latter if it will fit in either, i.e. for a fully dynamic matrix type). + static EigenConformable conformable(const array &a) { + const auto dims = a.ndim(); + if (dims < 1 || dims > 2) { + return false; + } + + if (dims == 2) { // Matrix type: require exact match (or dynamic) + + EigenIndex np_rows = a.shape(0), np_cols = a.shape(1), + np_rstride = a.strides(0) / static_cast(sizeof(Scalar)), + np_cstride = a.strides(1) / static_cast(sizeof(Scalar)); + if ((fixed_rows && np_rows != rows) || (fixed_cols && np_cols != cols)) { + return false; + } + + return {np_rows, np_cols, np_rstride, np_cstride}; + } + + // Otherwise we're storing an n-vector. Only one of the strides will be used, but + // whichever is used, we want the (single) numpy stride value. + const EigenIndex n = a.shape(0), + stride = a.strides(0) / static_cast(sizeof(Scalar)); + + if (vector) { // Eigen type is a compile-time vector + if (fixed && size != n) { + return false; // Vector size mismatch + } + return {rows == 1 ? 1 : n, cols == 1 ? 1 : n, stride}; + } + if (fixed) { + // The type has a fixed size, but is not a vector: abort + return false; + } + if (fixed_cols) { + // Since this isn't a vector, cols must be != 1. We allow this only if it exactly + // equals the number of elements (rows is Dynamic, and so 1 row is allowed). + if (cols != n) { + return false; + } + return {1, n, stride}; + } // Otherwise it's either fully dynamic, or column dynamic; both become a column vector + if (fixed_rows && rows != n) { + return false; + } + return {n, 1, stride}; + } + + static constexpr bool show_writeable + = is_eigen_dense_map::value && is_eigen_mutable_map::value; + static constexpr bool show_order = is_eigen_dense_map::value; + static constexpr bool show_c_contiguous = show_order && requires_row_major; + static constexpr bool show_f_contiguous + = !show_c_contiguous && show_order && requires_col_major; + + static constexpr auto descriptor + = const_name("numpy.ndarray[") + npy_format_descriptor::name + const_name("[") + + const_name(const_name<(size_t) rows>(), const_name("m")) + const_name(", ") + + const_name(const_name<(size_t) cols>(), const_name("n")) + const_name("]") + + + // For a reference type (e.g. Ref) we have other constraints that might need to + // be satisfied: writeable=True (for a mutable reference), and, depending on the map's + // stride options, possibly f_contiguous or c_contiguous. We include them in the + // descriptor output to provide some hint as to why a TypeError is occurring (otherwise + // it can be confusing to see that a function accepts a 'numpy.ndarray[float64[3,2]]' and + // an error message that you *gave* a numpy.ndarray of the right type and dimensions. + const_name(", flags.writeable", "") + + const_name(", flags.c_contiguous", "") + + const_name(", flags.f_contiguous", "") + const_name("]"); +}; + +// Casts an Eigen type to numpy array. If given a base, the numpy array references the src data, +// otherwise it'll make a copy. writeable lets you turn off the writeable flag for the array. +template +handle +eigen_array_cast(typename props::Type const &src, handle base = handle(), bool writeable = true) { + constexpr ssize_t elem_size = sizeof(typename props::Scalar); + array a; + if (props::vector) { + a = array({src.size()}, {elem_size * src.innerStride()}, src.data(), base); + } else { + a = array({src.rows(), src.cols()}, + {elem_size * src.rowStride(), elem_size * src.colStride()}, + src.data(), + base); + } + + if (!writeable) { + array_proxy(a.ptr())->flags &= ~detail::npy_api::NPY_ARRAY_WRITEABLE_; + } + + return a.release(); +} + +// Takes an lvalue ref to some Eigen type and a (python) base object, creating a numpy array that +// reference the Eigen object's data with `base` as the python-registered base class (if omitted, +// the base will be set to None, and lifetime management is up to the caller). The numpy array is +// non-writeable if the given type is const. +template +handle eigen_ref_array(Type &src, handle parent = none()) { + // none here is to get past array's should-we-copy detection, which currently always + // copies when there is no base. Setting the base to None should be harmless. + return eigen_array_cast(src, parent, !std::is_const::value); +} + +// Takes a pointer to some dense, plain Eigen type, builds a capsule around it, then returns a +// numpy array that references the encapsulated data with a python-side reference to the capsule to +// tie its destruction to that of any dependent python objects. Const-ness is determined by +// whether or not the Type of the pointer given is const. +template ::value>> +handle eigen_encapsulate(Type *src) { + capsule base(src, [](void *o) { delete static_cast(o); }); + return eigen_ref_array(*src, base); +} + +// Type caster for regular, dense matrix types (e.g. MatrixXd), but not maps/refs/etc. of dense +// types. +template +struct type_caster::value>> { + using Scalar = typename Type::Scalar; + static_assert(!std::is_pointer::value, + PYBIND11_EIGEN_MESSAGE_POINTER_TYPES_ARE_NOT_SUPPORTED); + using props = EigenProps; + + bool load(handle src, bool convert) { + // If we're in no-convert mode, only load if given an array of the correct type + if (!convert && !isinstance>(src)) { + return false; + } + + // Coerce into an array, but don't do type conversion yet; the copy below handles it. + auto buf = array::ensure(src); + + if (!buf) { + return false; + } + + auto dims = buf.ndim(); + if (dims < 1 || dims > 2) { + return false; + } + + auto fits = props::conformable(buf); + if (!fits) { + return false; + } + + // Allocate the new type, then build a numpy reference into it + value = Type(fits.rows, fits.cols); + auto ref = reinterpret_steal(eigen_ref_array(value)); + if (dims == 1) { + ref = ref.squeeze(); + } else if (ref.ndim() == 1) { + buf = buf.squeeze(); + } + + int result = detail::npy_api::get().PyArray_CopyInto_(ref.ptr(), buf.ptr()); + + if (result < 0) { // Copy failed! + PyErr_Clear(); + return false; + } + + return true; + } + +private: + // Cast implementation + template + static handle cast_impl(CType *src, return_value_policy policy, handle parent) { + switch (policy) { + case return_value_policy::take_ownership: + case return_value_policy::automatic: + return eigen_encapsulate(src); + case return_value_policy::move: + return eigen_encapsulate(new CType(std::move(*src))); + case return_value_policy::copy: + return eigen_array_cast(*src); + case return_value_policy::reference: + case return_value_policy::automatic_reference: + return eigen_ref_array(*src); + case return_value_policy::reference_internal: + return eigen_ref_array(*src, parent); + default: + throw cast_error("unhandled return_value_policy: should not happen!"); + }; + } + +public: + // Normal returned non-reference, non-const value: + static handle cast(Type &&src, return_value_policy /* policy */, handle parent) { + return cast_impl(&src, return_value_policy::move, parent); + } + // If you return a non-reference const, we mark the numpy array readonly: + static handle cast(const Type &&src, return_value_policy /* policy */, handle parent) { + return cast_impl(&src, return_value_policy::move, parent); + } + // lvalue reference return; default (automatic) becomes copy + static handle cast(Type &src, return_value_policy policy, handle parent) { + if (policy == return_value_policy::automatic + || policy == return_value_policy::automatic_reference) { + policy = return_value_policy::copy; + } + return cast_impl(&src, policy, parent); + } + // const lvalue reference return; default (automatic) becomes copy + static handle cast(const Type &src, return_value_policy policy, handle parent) { + if (policy == return_value_policy::automatic + || policy == return_value_policy::automatic_reference) { + policy = return_value_policy::copy; + } + return cast(&src, policy, parent); + } + // non-const pointer return + static handle cast(Type *src, return_value_policy policy, handle parent) { + return cast_impl(src, policy, parent); + } + // const pointer return + static handle cast(const Type *src, return_value_policy policy, handle parent) { + return cast_impl(src, policy, parent); + } + + static constexpr auto name = props::descriptor; + + // NOLINTNEXTLINE(google-explicit-constructor) + operator Type *() { return &value; } + // NOLINTNEXTLINE(google-explicit-constructor) + operator Type &() { return value; } + // NOLINTNEXTLINE(google-explicit-constructor) + operator Type &&() && { return std::move(value); } + template + using cast_op_type = movable_cast_op_type; + +private: + Type value; +}; + +// Base class for casting reference/map/block/etc. objects back to python. +template +struct eigen_map_caster { + static_assert(!std::is_pointer::value, + PYBIND11_EIGEN_MESSAGE_POINTER_TYPES_ARE_NOT_SUPPORTED); + +private: + using props = EigenProps; + +public: + // Directly referencing a ref/map's data is a bit dangerous (whatever the map/ref points to has + // to stay around), but we'll allow it under the assumption that you know what you're doing + // (and have an appropriate keep_alive in place). We return a numpy array pointing directly at + // the ref's data (The numpy array ends up read-only if the ref was to a const matrix type.) + // Note that this means you need to ensure you don't destroy the object in some other way (e.g. + // with an appropriate keep_alive, or with a reference to a statically allocated matrix). + static handle cast(const MapType &src, return_value_policy policy, handle parent) { + switch (policy) { + case return_value_policy::copy: + return eigen_array_cast(src); + case return_value_policy::reference_internal: + return eigen_array_cast(src, parent, is_eigen_mutable_map::value); + case return_value_policy::reference: + case return_value_policy::automatic: + case return_value_policy::automatic_reference: + return eigen_array_cast(src, none(), is_eigen_mutable_map::value); + default: + // move, take_ownership don't make any sense for a ref/map: + pybind11_fail("Invalid return_value_policy for Eigen Map/Ref/Block type"); + } + } + + static constexpr auto name = props::descriptor; + + // Explicitly delete these: support python -> C++ conversion on these (i.e. these can be return + // types but not bound arguments). We still provide them (with an explicitly delete) so that + // you end up here if you try anyway. + bool load(handle, bool) = delete; + operator MapType() = delete; + template + using cast_op_type = MapType; +}; + +// We can return any map-like object (but can only load Refs, specialized next): +template +struct type_caster::value>> : eigen_map_caster {}; + +// Loader for Ref<...> arguments. See the documentation for info on how to make this work without +// copying (it requires some extra effort in many cases). +template +struct type_caster< + Eigen::Ref, + enable_if_t>::value>> + : public eigen_map_caster> { +private: + using Type = Eigen::Ref; + using props = EigenProps; + using Scalar = typename props::Scalar; + static_assert(!std::is_pointer::value, + PYBIND11_EIGEN_MESSAGE_POINTER_TYPES_ARE_NOT_SUPPORTED); + using MapType = Eigen::Map; + using Array + = array_t; + static constexpr bool need_writeable = is_eigen_mutable_map::value; + // Delay construction (these have no default constructor) + std::unique_ptr map; + std::unique_ptr ref; + // Our array. When possible, this is just a numpy array pointing to the source data, but + // sometimes we can't avoid copying (e.g. input is not a numpy array at all, has an + // incompatible layout, or is an array of a type that needs to be converted). Using a numpy + // temporary (rather than an Eigen temporary) saves an extra copy when we need both type + // conversion and storage order conversion. (Note that we refuse to use this temporary copy + // when loading an argument for a Ref with M non-const, i.e. a read-write reference). + Array copy_or_ref; + +public: + bool load(handle src, bool convert) { + // First check whether what we have is already an array of the right type. If not, we + // can't avoid a copy (because the copy is also going to do type conversion). + bool need_copy = !isinstance(src); + + EigenConformable fits; + if (!need_copy) { + // We don't need a converting copy, but we also need to check whether the strides are + // compatible with the Ref's stride requirements + auto aref = reinterpret_borrow(src); + + if (aref && (!need_writeable || aref.writeable())) { + fits = props::conformable(aref); + if (!fits) { + return false; // Incompatible dimensions + } + if (!fits.template stride_compatible()) { + need_copy = true; + } else { + copy_or_ref = std::move(aref); + } + } else { + need_copy = true; + } + } + + if (need_copy) { + // We need to copy: If we need a mutable reference, or we're not supposed to convert + // (either because we're in the no-convert overload pass, or because we're explicitly + // instructed not to copy (via `py::arg().noconvert()`) we have to fail loading. + if (!convert || need_writeable) { + return false; + } + + Array copy = Array::ensure(src); + if (!copy) { + return false; + } + fits = props::conformable(copy); + if (!fits || !fits.template stride_compatible()) { + return false; + } + copy_or_ref = std::move(copy); + loader_life_support::add_patient(copy_or_ref); + } + + ref.reset(); + map.reset(new MapType(data(copy_or_ref), + fits.rows, + fits.cols, + make_stride(fits.stride.outer(), fits.stride.inner()))); + ref.reset(new Type(*map)); + + return true; + } + + // NOLINTNEXTLINE(google-explicit-constructor) + operator Type *() { return ref.get(); } + // NOLINTNEXTLINE(google-explicit-constructor) + operator Type &() { return *ref; } + template + using cast_op_type = pybind11::detail::cast_op_type<_T>; + +private: + template ::value, int> = 0> + Scalar *data(Array &a) { + return a.mutable_data(); + } + + template ::value, int> = 0> + const Scalar *data(Array &a) { + return a.data(); + } + + // Attempt to figure out a constructor of `Stride` that will work. + // If both strides are fixed, use a default constructor: + template + using stride_ctor_default = bool_constant::value>; + // Otherwise, if there is a two-index constructor, assume it is (outer,inner) like + // Eigen::Stride, and use it: + template + using stride_ctor_dual + = bool_constant::value + && std::is_constructible::value>; + // Otherwise, if there is a one-index constructor, and just one of the strides is dynamic, use + // it (passing whichever stride is dynamic). + template + using stride_ctor_outer + = bool_constant, stride_ctor_dual>::value + && S::OuterStrideAtCompileTime == Eigen::Dynamic + && S::InnerStrideAtCompileTime != Eigen::Dynamic + && std::is_constructible::value>; + template + using stride_ctor_inner + = bool_constant, stride_ctor_dual>::value + && S::InnerStrideAtCompileTime == Eigen::Dynamic + && S::OuterStrideAtCompileTime != Eigen::Dynamic + && std::is_constructible::value>; + + template ::value, int> = 0> + static S make_stride(EigenIndex, EigenIndex) { + return S(); + } + template ::value, int> = 0> + static S make_stride(EigenIndex outer, EigenIndex inner) { + return S(outer, inner); + } + template ::value, int> = 0> + static S make_stride(EigenIndex outer, EigenIndex) { + return S(outer); + } + template ::value, int> = 0> + static S make_stride(EigenIndex, EigenIndex inner) { + return S(inner); + } +}; + +// type_caster for special matrix types (e.g. DiagonalMatrix), which are EigenBase, but not +// EigenDense (i.e. they don't have a data(), at least not with the usual matrix layout). +// load() is not supported, but we can cast them into the python domain by first copying to a +// regular Eigen::Matrix, then casting that. +template +struct type_caster::value>> { + static_assert(!std::is_pointer::value, + PYBIND11_EIGEN_MESSAGE_POINTER_TYPES_ARE_NOT_SUPPORTED); + +protected: + using Matrix + = Eigen::Matrix; + using props = EigenProps; + +public: + static handle cast(const Type &src, return_value_policy /* policy */, handle /* parent */) { + handle h = eigen_encapsulate(new Matrix(src)); + return h; + } + static handle cast(const Type *src, return_value_policy policy, handle parent) { + return cast(*src, policy, parent); + } + + static constexpr auto name = props::descriptor; + + // Explicitly delete these: support python -> C++ conversion on these (i.e. these can be return + // types but not bound arguments). We still provide them (with an explicitly delete) so that + // you end up here if you try anyway. + bool load(handle, bool) = delete; + operator Type() = delete; + template + using cast_op_type = Type; +}; + +template +struct type_caster::value>> { + using Scalar = typename Type::Scalar; + static_assert(!std::is_pointer::value, + PYBIND11_EIGEN_MESSAGE_POINTER_TYPES_ARE_NOT_SUPPORTED); + using StorageIndex = remove_reference_t().outerIndexPtr())>; + using Index = typename Type::Index; + static constexpr bool rowMajor = Type::IsRowMajor; + + bool load(handle src, bool) { + if (!src) { + return false; + } + + auto obj = reinterpret_borrow(src); + object sparse_module = module_::import("scipy.sparse"); + object matrix_type = sparse_module.attr(rowMajor ? "csr_matrix" : "csc_matrix"); + + if (!type::handle_of(obj).is(matrix_type)) { + try { + obj = matrix_type(obj); + } catch (const error_already_set &) { + return false; + } + } + + auto values = array_t((object) obj.attr("data")); + auto innerIndices = array_t((object) obj.attr("indices")); + auto outerIndices = array_t((object) obj.attr("indptr")); + auto shape = pybind11::tuple((pybind11::object) obj.attr("shape")); + auto nnz = obj.attr("nnz").cast(); + + if (!values || !innerIndices || !outerIndices) { + return false; + } + + value = EigenMapSparseMatrix(shape[0].cast(), + shape[1].cast(), + std::move(nnz), + outerIndices.mutable_data(), + innerIndices.mutable_data(), + values.mutable_data()); + + return true; + } + + static handle cast(const Type &src, return_value_policy /* policy */, handle /* parent */) { + const_cast(src).makeCompressed(); + + object matrix_type + = module_::import("scipy.sparse").attr(rowMajor ? "csr_matrix" : "csc_matrix"); + + array data(src.nonZeros(), src.valuePtr()); + array outerIndices((rowMajor ? src.rows() : src.cols()) + 1, src.outerIndexPtr()); + array innerIndices(src.nonZeros(), src.innerIndexPtr()); + + return matrix_type(pybind11::make_tuple( + std::move(data), std::move(innerIndices), std::move(outerIndices)), + pybind11::make_tuple(src.rows(), src.cols())) + .release(); + } + + PYBIND11_TYPE_CASTER(Type, + const_name<(Type::IsRowMajor) != 0>("scipy.sparse.csr_matrix[", + "scipy.sparse.csc_matrix[") + + npy_format_descriptor::name + const_name("]")); +}; + +PYBIND11_NAMESPACE_END(detail) +PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE) diff --git a/pybind11/include/pybind11/eigen/tensor.h b/pybind11/include/pybind11/eigen/tensor.h new file mode 100644 index 000000000..25d12baca --- /dev/null +++ b/pybind11/include/pybind11/eigen/tensor.h @@ -0,0 +1,516 @@ +/* + pybind11/eigen/tensor.h: Transparent conversion for Eigen tensors + + All rights reserved. Use of this source code is governed by a + BSD-style license that can be found in the LICENSE file. +*/ + +#pragma once + +#include "../numpy.h" +#include "common.h" + +#if defined(__GNUC__) && !defined(__clang__) && !defined(__INTEL_COMPILER) +static_assert(__GNUC__ > 5, "Eigen Tensor support in pybind11 requires GCC > 5.0"); +#endif + +// Disable warnings for Eigen +PYBIND11_WARNING_PUSH +PYBIND11_WARNING_DISABLE_MSVC(4554) +PYBIND11_WARNING_DISABLE_MSVC(4127) +#if defined(__MINGW32__) +PYBIND11_WARNING_DISABLE_GCC("-Wmaybe-uninitialized") +#endif + +#include + +PYBIND11_WARNING_POP + +static_assert(EIGEN_VERSION_AT_LEAST(3, 3, 0), + "Eigen Tensor support in pybind11 requires Eigen >= 3.3.0"); + +PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE) + +PYBIND11_WARNING_DISABLE_MSVC(4127) + +PYBIND11_NAMESPACE_BEGIN(detail) + +inline bool is_tensor_aligned(const void *data) { + return (reinterpret_cast(data) % EIGEN_DEFAULT_ALIGN_BYTES) == 0; +} + +template +constexpr int compute_array_flag_from_tensor() { + static_assert((static_cast(T::Layout) == static_cast(Eigen::RowMajor)) + || (static_cast(T::Layout) == static_cast(Eigen::ColMajor)), + "Layout must be row or column major"); + return (static_cast(T::Layout) == static_cast(Eigen::RowMajor)) ? array::c_style + : array::f_style; +} + +template +struct eigen_tensor_helper {}; + +template +struct eigen_tensor_helper> { + using Type = Eigen::Tensor; + using ValidType = void; + + static Eigen::DSizes get_shape(const Type &f) { + return f.dimensions(); + } + + static constexpr bool + is_correct_shape(const Eigen::DSizes & /*shape*/) { + return true; + } + + template + struct helper {}; + + template + struct helper> { + static constexpr auto value = concat(const_name(((void) Is, "?"))...); + }; + + static constexpr auto dimensions_descriptor + = helper())>::value; + + template + static Type *alloc(Args &&...args) { + return new Type(std::forward(args)...); + } + + static void free(Type *tensor) { delete tensor; } +}; + +template +struct eigen_tensor_helper< + Eigen::TensorFixedSize, Options_, IndexType>> { + using Type = Eigen::TensorFixedSize, Options_, IndexType>; + using ValidType = void; + + static constexpr Eigen::DSizes + get_shape(const Type & /*f*/) { + return get_shape(); + } + + static constexpr Eigen::DSizes get_shape() { + return Eigen::DSizes(Indices...); + } + + static bool + is_correct_shape(const Eigen::DSizes &shape) { + return get_shape() == shape; + } + + static constexpr auto dimensions_descriptor = concat(const_name()...); + + template + static Type *alloc(Args &&...args) { + Eigen::aligned_allocator allocator; + return ::new (allocator.allocate(1)) Type(std::forward(args)...); + } + + static void free(Type *tensor) { + Eigen::aligned_allocator allocator; + tensor->~Type(); + allocator.deallocate(tensor, 1); + } +}; + +template +struct get_tensor_descriptor { + static constexpr auto details + = const_name(", flags.writeable", "") + + const_name(Type::Layout) == static_cast(Eigen::RowMajor)>( + ", flags.c_contiguous", ", flags.f_contiguous"); + static constexpr auto value + = const_name("numpy.ndarray[") + npy_format_descriptor::name + + const_name("[") + eigen_tensor_helper>::dimensions_descriptor + + const_name("]") + const_name(details, const_name("")) + const_name("]"); +}; + +// When EIGEN_AVOID_STL_ARRAY is defined, Eigen::DSizes does not have the begin() member +// function. Falling back to a simple loop works around this issue. +// +// We need to disable the type-limits warning for the inner loop when size = 0. + +PYBIND11_WARNING_PUSH +PYBIND11_WARNING_DISABLE_GCC("-Wtype-limits") + +template +std::vector convert_dsizes_to_vector(const Eigen::DSizes &arr) { + std::vector result(size); + + for (size_t i = 0; i < size; i++) { + result[i] = arr[i]; + } + + return result; +} + +template +Eigen::DSizes get_shape_for_array(const array &arr) { + Eigen::DSizes result; + const T *shape = arr.shape(); + for (size_t i = 0; i < size; i++) { + result[i] = shape[i]; + } + + return result; +} + +PYBIND11_WARNING_POP + +template +struct type_caster::ValidType> { + static_assert(!std::is_pointer::value, + PYBIND11_EIGEN_MESSAGE_POINTER_TYPES_ARE_NOT_SUPPORTED); + using Helper = eigen_tensor_helper; + static constexpr auto temp_name = get_tensor_descriptor::value; + PYBIND11_TYPE_CASTER(Type, temp_name); + + bool load(handle src, bool convert) { + if (!convert) { + if (!isinstance(src)) { + return false; + } + array temp = array::ensure(src); + if (!temp) { + return false; + } + + if (!temp.dtype().is(dtype::of())) { + return false; + } + } + + array_t()> arr( + reinterpret_borrow(src)); + + if (arr.ndim() != Type::NumIndices) { + return false; + } + auto shape = get_shape_for_array(arr); + + if (!Helper::is_correct_shape(shape)) { + return false; + } + +#if EIGEN_VERSION_AT_LEAST(3, 4, 0) + auto data_pointer = arr.data(); +#else + // Handle Eigen bug + auto data_pointer = const_cast(arr.data()); +#endif + + if (is_tensor_aligned(arr.data())) { + value = Eigen::TensorMap(data_pointer, shape); + } else { + value = Eigen::TensorMap(data_pointer, shape); + } + + return true; + } + + static handle cast(Type &&src, return_value_policy policy, handle parent) { + if (policy == return_value_policy::reference + || policy == return_value_policy::reference_internal) { + pybind11_fail("Cannot use a reference return value policy for an rvalue"); + } + return cast_impl(&src, return_value_policy::move, parent); + } + + static handle cast(const Type &&src, return_value_policy policy, handle parent) { + if (policy == return_value_policy::reference + || policy == return_value_policy::reference_internal) { + pybind11_fail("Cannot use a reference return value policy for an rvalue"); + } + return cast_impl(&src, return_value_policy::move, parent); + } + + static handle cast(Type &src, return_value_policy policy, handle parent) { + if (policy == return_value_policy::automatic + || policy == return_value_policy::automatic_reference) { + policy = return_value_policy::copy; + } + return cast_impl(&src, policy, parent); + } + + static handle cast(const Type &src, return_value_policy policy, handle parent) { + if (policy == return_value_policy::automatic + || policy == return_value_policy::automatic_reference) { + policy = return_value_policy::copy; + } + return cast(&src, policy, parent); + } + + static handle cast(Type *src, return_value_policy policy, handle parent) { + if (policy == return_value_policy::automatic) { + policy = return_value_policy::take_ownership; + } else if (policy == return_value_policy::automatic_reference) { + policy = return_value_policy::reference; + } + return cast_impl(src, policy, parent); + } + + static handle cast(const Type *src, return_value_policy policy, handle parent) { + if (policy == return_value_policy::automatic) { + policy = return_value_policy::take_ownership; + } else if (policy == return_value_policy::automatic_reference) { + policy = return_value_policy::reference; + } + return cast_impl(src, policy, parent); + } + + template + static handle cast_impl(C *src, return_value_policy policy, handle parent) { + object parent_object; + bool writeable = false; + switch (policy) { + case return_value_policy::move: + if (std::is_const::value) { + pybind11_fail("Cannot move from a constant reference"); + } + + src = Helper::alloc(std::move(*src)); + + parent_object + = capsule(src, [](void *ptr) { Helper::free(reinterpret_cast(ptr)); }); + writeable = true; + break; + + case return_value_policy::take_ownership: + if (std::is_const::value) { + // This cast is ugly, and might be UB in some cases, but we don't have an + // alternative here as we must free that memory + Helper::free(const_cast(src)); + pybind11_fail("Cannot take ownership of a const reference"); + } + + parent_object + = capsule(src, [](void *ptr) { Helper::free(reinterpret_cast(ptr)); }); + writeable = true; + break; + + case return_value_policy::copy: + writeable = true; + break; + + case return_value_policy::reference: + parent_object = none(); + writeable = !std::is_const::value; + break; + + case return_value_policy::reference_internal: + // Default should do the right thing + if (!parent) { + pybind11_fail("Cannot use reference internal when there is no parent"); + } + parent_object = reinterpret_borrow(parent); + writeable = !std::is_const::value; + break; + + default: + pybind11_fail("pybind11 bug in eigen.h, please file a bug report"); + } + + auto result = array_t()>( + convert_dsizes_to_vector(Helper::get_shape(*src)), src->data(), parent_object); + + if (!writeable) { + array_proxy(result.ptr())->flags &= ~detail::npy_api::NPY_ARRAY_WRITEABLE_; + } + + return result.release(); + } +}; + +template = true> +StoragePointerType get_array_data_for_type(array &arr) { +#if EIGEN_VERSION_AT_LEAST(3, 4, 0) + return reinterpret_cast(arr.data()); +#else + // Handle Eigen bug + return reinterpret_cast(const_cast(arr.data())); +#endif +} + +template = true> +StoragePointerType get_array_data_for_type(array &arr) { + return reinterpret_cast(arr.mutable_data()); +} + +template +struct get_storage_pointer_type; + +template +struct get_storage_pointer_type> { + using SPT = typename MapType::StoragePointerType; +}; + +template +struct get_storage_pointer_type> { + using SPT = typename MapType::PointerArgType; +}; + +template +struct type_caster, + typename eigen_tensor_helper>::ValidType> { + static_assert(!std::is_pointer::value, + PYBIND11_EIGEN_MESSAGE_POINTER_TYPES_ARE_NOT_SUPPORTED); + using MapType = Eigen::TensorMap; + using Helper = eigen_tensor_helper>; + + bool load(handle src, bool /*convert*/) { + // Note that we have a lot more checks here as we want to make sure to avoid copies + if (!isinstance(src)) { + return false; + } + auto arr = reinterpret_borrow(src); + if ((arr.flags() & compute_array_flag_from_tensor()) == 0) { + return false; + } + + if (!arr.dtype().is(dtype::of())) { + return false; + } + + if (arr.ndim() != Type::NumIndices) { + return false; + } + + constexpr bool is_aligned = (Options & Eigen::Aligned) != 0; + + if (is_aligned && !is_tensor_aligned(arr.data())) { + return false; + } + + auto shape = get_shape_for_array(arr); + + if (!Helper::is_correct_shape(shape)) { + return false; + } + + if (needs_writeable && !arr.writeable()) { + return false; + } + + auto result = get_array_data_for_type::SPT, + needs_writeable>(arr); + + value.reset(new MapType(std::move(result), std::move(shape))); + + return true; + } + + static handle cast(MapType &&src, return_value_policy policy, handle parent) { + return cast_impl(&src, policy, parent); + } + + static handle cast(const MapType &&src, return_value_policy policy, handle parent) { + return cast_impl(&src, policy, parent); + } + + static handle cast(MapType &src, return_value_policy policy, handle parent) { + if (policy == return_value_policy::automatic + || policy == return_value_policy::automatic_reference) { + policy = return_value_policy::copy; + } + return cast_impl(&src, policy, parent); + } + + static handle cast(const MapType &src, return_value_policy policy, handle parent) { + if (policy == return_value_policy::automatic + || policy == return_value_policy::automatic_reference) { + policy = return_value_policy::copy; + } + return cast(&src, policy, parent); + } + + static handle cast(MapType *src, return_value_policy policy, handle parent) { + if (policy == return_value_policy::automatic) { + policy = return_value_policy::take_ownership; + } else if (policy == return_value_policy::automatic_reference) { + policy = return_value_policy::reference; + } + return cast_impl(src, policy, parent); + } + + static handle cast(const MapType *src, return_value_policy policy, handle parent) { + if (policy == return_value_policy::automatic) { + policy = return_value_policy::take_ownership; + } else if (policy == return_value_policy::automatic_reference) { + policy = return_value_policy::reference; + } + return cast_impl(src, policy, parent); + } + + template + static handle cast_impl(C *src, return_value_policy policy, handle parent) { + object parent_object; + constexpr bool writeable = !std::is_const::value; + switch (policy) { + case return_value_policy::reference: + parent_object = none(); + break; + + case return_value_policy::reference_internal: + // Default should do the right thing + if (!parent) { + pybind11_fail("Cannot use reference internal when there is no parent"); + } + parent_object = reinterpret_borrow(parent); + break; + + case return_value_policy::take_ownership: + delete src; + // fallthrough + default: + // move, take_ownership don't make any sense for a ref/map: + pybind11_fail("Invalid return_value_policy for Eigen Map type, must be either " + "reference or reference_internal"); + } + + auto result = array_t()>( + convert_dsizes_to_vector(Helper::get_shape(*src)), + src->data(), + std::move(parent_object)); + + if (!writeable) { + array_proxy(result.ptr())->flags &= ~detail::npy_api::NPY_ARRAY_WRITEABLE_; + } + + return result.release(); + } + +#if EIGEN_VERSION_AT_LEAST(3, 4, 0) + + static constexpr bool needs_writeable = !std::is_const::SPT>::type>::value; +#else + // Handle Eigen bug + static constexpr bool needs_writeable = !std::is_const::value; +#endif + +protected: + // TODO: Move to std::optional once std::optional has more support + std::unique_ptr value; + +public: + static constexpr auto name = get_tensor_descriptor::value; + explicit operator MapType *() { return value.get(); } + explicit operator MapType &() { return *value; } + explicit operator MapType &&() && { return std::move(*value); } + + template + using cast_op_type = ::pybind11::detail::movable_cast_op_type; +}; + +PYBIND11_NAMESPACE_END(detail) +PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE) diff --git a/pybind11/include/pybind11/embed.h b/pybind11/include/pybind11/embed.h index d6999cd77..caa14f4a0 100644 --- a/pybind11/include/pybind11/embed.h +++ b/pybind11/include/pybind11/embed.h @@ -86,38 +86,26 @@ inline wchar_t *widen_chars(const char *safe_arg) { return widened_arg; } -PYBIND11_NAMESPACE_END(detail) - -/** \rst - Initialize the Python interpreter. No other pybind11 or CPython API functions can be - called before this is done; with the exception of `PYBIND11_EMBEDDED_MODULE`. The - optional `init_signal_handlers` parameter can be used to skip the registration of - signal handlers (see the `Python documentation`_ for details). Calling this function - again after the interpreter has already been initialized is a fatal error. - - If initializing the Python interpreter fails, then the program is terminated. (This - is controlled by the CPython runtime and is an exception to pybind11's normal behavior - of throwing exceptions on errors.) - - The remaining optional parameters, `argc`, `argv`, and `add_program_dir_to_path` are - used to populate ``sys.argv`` and ``sys.path``. - See the |PySys_SetArgvEx documentation|_ for details. - - .. _Python documentation: https://docs.python.org/3/c-api/init.html#c.Py_InitializeEx - .. |PySys_SetArgvEx documentation| replace:: ``PySys_SetArgvEx`` documentation - .. _PySys_SetArgvEx documentation: https://docs.python.org/3/c-api/init.html#c.PySys_SetArgvEx - \endrst */ -inline void initialize_interpreter(bool init_signal_handlers = true, - int argc = 0, - const char *const *argv = nullptr, - bool add_program_dir_to_path = true) { +inline void precheck_interpreter() { if (Py_IsInitialized() != 0) { pybind11_fail("The interpreter is already running"); } +} -#if PY_VERSION_HEX < 0x030B0000 +#if !defined(PYBIND11_PYCONFIG_SUPPORT_PY_VERSION_HEX) +# define PYBIND11_PYCONFIG_SUPPORT_PY_VERSION_HEX (0x03080000) +#endif +#if PY_VERSION_HEX < PYBIND11_PYCONFIG_SUPPORT_PY_VERSION_HEX +inline void initialize_interpreter_pre_pyconfig(bool init_signal_handlers, + int argc, + const char *const *argv, + bool add_program_dir_to_path) { + detail::precheck_interpreter(); Py_InitializeEx(init_signal_handlers ? 1 : 0); +# if defined(WITH_THREAD) && PY_VERSION_HEX < 0x03070000 + PyEval_InitThreads(); +# endif // Before it was special-cased in python 3.8, passing an empty or null argv // caused a segfault, so we have to reimplement the special case ourselves. @@ -147,24 +135,30 @@ inline void initialize_interpreter(bool init_signal_handlers = true, auto *pysys_argv = widened_argv.get(); PySys_SetArgvEx(argc, pysys_argv, static_cast(add_program_dir_to_path)); -#else - PyConfig config; - PyConfig_InitIsolatedConfig(&config); - config.install_signal_handlers = init_signal_handlers ? 1 : 0; +} +#endif - PyStatus status = PyConfig_SetBytesArgv(&config, argc, const_cast(argv)); - if (PyStatus_Exception(status)) { +PYBIND11_NAMESPACE_END(detail) + +#if PY_VERSION_HEX >= PYBIND11_PYCONFIG_SUPPORT_PY_VERSION_HEX +inline void initialize_interpreter(PyConfig *config, + int argc = 0, + const char *const *argv = nullptr, + bool add_program_dir_to_path = true) { + detail::precheck_interpreter(); + PyStatus status = PyConfig_SetBytesArgv(config, argc, const_cast(argv)); + if (PyStatus_Exception(status) != 0) { // A failure here indicates a character-encoding failure or the python // interpreter out of memory. Give up. - PyConfig_Clear(&config); - throw std::runtime_error(PyStatus_IsError(status) ? status.err_msg - : "Failed to prepare CPython"); + PyConfig_Clear(config); + throw std::runtime_error(PyStatus_IsError(status) != 0 ? status.err_msg + : "Failed to prepare CPython"); } - status = Py_InitializeFromConfig(&config); - PyConfig_Clear(&config); - if (PyStatus_Exception(status)) { - throw std::runtime_error(PyStatus_IsError(status) ? status.err_msg - : "Failed to init CPython"); + status = Py_InitializeFromConfig(config); + if (PyStatus_Exception(status) != 0) { + PyConfig_Clear(config); + throw std::runtime_error(PyStatus_IsError(status) != 0 ? status.err_msg + : "Failed to init CPython"); } if (add_program_dir_to_path) { PyRun_SimpleString("import sys, os.path; " @@ -172,6 +166,44 @@ inline void initialize_interpreter(bool init_signal_handlers = true, "os.path.abspath(os.path.dirname(sys.argv[0])) " "if sys.argv and os.path.exists(sys.argv[0]) else '')"); } + PyConfig_Clear(config); +} +#endif + +/** \rst + Initialize the Python interpreter. No other pybind11 or CPython API functions can be + called before this is done; with the exception of `PYBIND11_EMBEDDED_MODULE`. The + optional `init_signal_handlers` parameter can be used to skip the registration of + signal handlers (see the `Python documentation`_ for details). Calling this function + again after the interpreter has already been initialized is a fatal error. + + If initializing the Python interpreter fails, then the program is terminated. (This + is controlled by the CPython runtime and is an exception to pybind11's normal behavior + of throwing exceptions on errors.) + + The remaining optional parameters, `argc`, `argv`, and `add_program_dir_to_path` are + used to populate ``sys.argv`` and ``sys.path``. + See the |PySys_SetArgvEx documentation|_ for details. + + .. _Python documentation: https://docs.python.org/3/c-api/init.html#c.Py_InitializeEx + .. |PySys_SetArgvEx documentation| replace:: ``PySys_SetArgvEx`` documentation + .. _PySys_SetArgvEx documentation: https://docs.python.org/3/c-api/init.html#c.PySys_SetArgvEx + \endrst */ +inline void initialize_interpreter(bool init_signal_handlers = true, + int argc = 0, + const char *const *argv = nullptr, + bool add_program_dir_to_path = true) { +#if PY_VERSION_HEX < PYBIND11_PYCONFIG_SUPPORT_PY_VERSION_HEX + detail::initialize_interpreter_pre_pyconfig( + init_signal_handlers, argc, argv, add_program_dir_to_path); +#else + PyConfig config; + PyConfig_InitPythonConfig(&config); + // See PR #4473 for background + config.parse_argv = 0; + + config.install_signal_handlers = init_signal_handlers ? 1 : 0; + initialize_interpreter(&config, argc, argv, add_program_dir_to_path); #endif } @@ -211,16 +243,14 @@ inline void initialize_interpreter(bool init_signal_handlers = true, \endrst */ inline void finalize_interpreter() { - handle builtins(PyEval_GetBuiltins()); - const char *id = PYBIND11_INTERNALS_ID; - // Get the internals pointer (without creating it if it doesn't exist). It's possible for the // internals to be created during Py_Finalize() (e.g. if a py::capsule calls `get_internals()` // during destruction), so we get the pointer-pointer here and check it after Py_Finalize(). detail::internals **internals_ptr_ptr = detail::get_internals_pp(); - // It could also be stashed in builtins, so look there too: - if (builtins.contains(id) && isinstance(builtins[id])) { - internals_ptr_ptr = capsule(builtins[id]); + // It could also be stashed in state_dict, so look there too: + if (object internals_obj + = get_internals_obj_from_state_dict(detail::get_python_state_dict())) { + internals_ptr_ptr = detail::get_internals_pp_from_capsule(internals_obj); } // Local internals contains data managed by the current interpreter, so we must clear them to // avoid undefined behaviors when initializing another interpreter @@ -259,6 +289,15 @@ public: initialize_interpreter(init_signal_handlers, argc, argv, add_program_dir_to_path); } +#if PY_VERSION_HEX >= PYBIND11_PYCONFIG_SUPPORT_PY_VERSION_HEX + explicit scoped_interpreter(PyConfig *config, + int argc = 0, + const char *const *argv = nullptr, + bool add_program_dir_to_path = true) { + initialize_interpreter(config, argc, argv, add_program_dir_to_path); + } +#endif + scoped_interpreter(const scoped_interpreter &) = delete; scoped_interpreter(scoped_interpreter &&other) noexcept { other.is_valid = false; } scoped_interpreter &operator=(const scoped_interpreter &) = delete; diff --git a/pybind11/include/pybind11/functional.h b/pybind11/include/pybind11/functional.h index 4034990d8..87ec4d10c 100644 --- a/pybind11/include/pybind11/functional.h +++ b/pybind11/include/pybind11/functional.h @@ -48,9 +48,16 @@ public: */ if (auto cfunc = func.cpp_function()) { auto *cfunc_self = PyCFunction_GET_SELF(cfunc.ptr()); - if (isinstance(cfunc_self)) { + if (cfunc_self == nullptr) { + PyErr_Clear(); + } else if (isinstance(cfunc_self)) { auto c = reinterpret_borrow(cfunc_self); - auto *rec = (function_record *) c; + + function_record *rec = nullptr; + // Check that we can safely reinterpret the capsule into a function_record + if (detail::is_function_record_capsule(c)) { + rec = c.get_pointer(); + } while (rec != nullptr) { if (rec->is_stateless @@ -110,7 +117,7 @@ public: template static handle cast(Func &&f_, return_value_policy policy, handle /* parent */) { if (!f_) { - return none().inc_ref(); + return none().release(); } auto result = f_.template target(); diff --git a/pybind11/include/pybind11/gil.h b/pybind11/include/pybind11/gil.h index a0b5de151..570a5581d 100644 --- a/pybind11/include/pybind11/gil.h +++ b/pybind11/include/pybind11/gil.h @@ -10,7 +10,10 @@ #pragma once #include "detail/common.h" -#include "detail/internals.h" + +#if defined(WITH_THREAD) && !defined(PYBIND11_SIMPLE_GIL_MANAGEMENT) +# include "detail/internals.h" +#endif PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE) @@ -21,7 +24,9 @@ PyThreadState *get_thread_state_unchecked(); PYBIND11_NAMESPACE_END(detail) -#if defined(WITH_THREAD) && !defined(PYPY_VERSION) +#if defined(WITH_THREAD) + +# if !defined(PYBIND11_SIMPLE_GIL_MANAGEMENT) /* The functions below essentially reproduce the PyGILState_* API using a RAII * pattern, but there are a few important differences: @@ -62,11 +67,11 @@ public: if (!tstate) { tstate = PyThreadState_New(internals.istate); -# if defined(PYBIND11_DETAILED_ERROR_MESSAGES) +# if defined(PYBIND11_DETAILED_ERROR_MESSAGES) if (!tstate) { pybind11_fail("scoped_acquire: could not create thread state!"); } -# endif +# endif tstate->gilstate_counter = 0; PYBIND11_TLS_REPLACE_VALUE(internals.tstate, tstate); } else { @@ -80,24 +85,27 @@ public: inc_ref(); } + gil_scoped_acquire(const gil_scoped_acquire &) = delete; + gil_scoped_acquire &operator=(const gil_scoped_acquire &) = delete; + void inc_ref() { ++tstate->gilstate_counter; } PYBIND11_NOINLINE void dec_ref() { --tstate->gilstate_counter; -# if defined(PYBIND11_DETAILED_ERROR_MESSAGES) +# if defined(PYBIND11_DETAILED_ERROR_MESSAGES) if (detail::get_thread_state_unchecked() != tstate) { pybind11_fail("scoped_acquire::dec_ref(): thread state must be current!"); } if (tstate->gilstate_counter < 0) { pybind11_fail("scoped_acquire::dec_ref(): reference count underflow!"); } -# endif +# endif if (tstate->gilstate_counter == 0) { -# if defined(PYBIND11_DETAILED_ERROR_MESSAGES) +# if defined(PYBIND11_DETAILED_ERROR_MESSAGES) if (!release) { pybind11_fail("scoped_acquire::dec_ref(): internal error!"); } -# endif +# endif PyThreadState_Clear(tstate); if (active) { PyThreadState_DeleteCurrent(); @@ -144,6 +152,9 @@ public: } } + gil_scoped_release(const gil_scoped_release &) = delete; + gil_scoped_release &operator=(const gil_scoped_release &) = delete; + /// This method will disable the PyThreadState_DeleteCurrent call and the /// GIL won't be acquired. This method should be used if the interpreter /// could be shutting down when this is called, as thread deletion is not @@ -172,12 +183,16 @@ private: bool disassoc; bool active = true; }; -#elif defined(PYPY_VERSION) + +# else // PYBIND11_SIMPLE_GIL_MANAGEMENT + class gil_scoped_acquire { PyGILState_STATE state; public: - gil_scoped_acquire() { state = PyGILState_Ensure(); } + gil_scoped_acquire() : state{PyGILState_Ensure()} {} + gil_scoped_acquire(const gil_scoped_acquire &) = delete; + gil_scoped_acquire &operator=(const gil_scoped_acquire &) = delete; ~gil_scoped_acquire() { PyGILState_Release(state); } void disarm() {} }; @@ -186,17 +201,39 @@ class gil_scoped_release { PyThreadState *state; public: - gil_scoped_release() { state = PyEval_SaveThread(); } + gil_scoped_release() : state{PyEval_SaveThread()} {} + gil_scoped_release(const gil_scoped_release &) = delete; + gil_scoped_release &operator=(const gil_scoped_release &) = delete; ~gil_scoped_release() { PyEval_RestoreThread(state); } void disarm() {} }; -#else + +# endif // PYBIND11_SIMPLE_GIL_MANAGEMENT + +#else // WITH_THREAD + class gil_scoped_acquire { +public: + gil_scoped_acquire() { + // Trick to suppress `unused variable` error messages (at call sites). + (void) (this != (this + 1)); + } + gil_scoped_acquire(const gil_scoped_acquire &) = delete; + gil_scoped_acquire &operator=(const gil_scoped_acquire &) = delete; void disarm() {} }; + class gil_scoped_release { +public: + gil_scoped_release() { + // Trick to suppress `unused variable` error messages (at call sites). + (void) (this != (this + 1)); + } + gil_scoped_release(const gil_scoped_release &) = delete; + gil_scoped_release &operator=(const gil_scoped_release &) = delete; void disarm() {} }; -#endif + +#endif // WITH_THREAD PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE) diff --git a/pybind11/include/pybind11/numpy.h b/pybind11/include/pybind11/numpy.h index 0291b02d0..36077ec04 100644 --- a/pybind11/include/pybind11/numpy.h +++ b/pybind11/include/pybind11/numpy.h @@ -36,6 +36,8 @@ static_assert(std::is_signed::value, "Py_intptr_t must be signed"); PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE) +PYBIND11_WARNING_DISABLE_MSVC(4127) + class array; // Forward declaration PYBIND11_NAMESPACE_BEGIN(detail) @@ -537,7 +539,7 @@ PYBIND11_NAMESPACE_END(detail) class dtype : public object { public: - PYBIND11_OBJECT_DEFAULT(dtype, object, detail::npy_api::get().PyArrayDescr_Check_); + PYBIND11_OBJECT_DEFAULT(dtype, object, detail::npy_api::get().PyArrayDescr_Check_) explicit dtype(const buffer_info &info) { dtype descr(_dtype_from_pep3118()(pybind11::str(info.format))); @@ -562,6 +564,8 @@ public: m_ptr = from_args(args).release().ptr(); } + /// Return dtype for the given typenum (one of the NPY_TYPES). + /// https://numpy.org/devdocs/reference/c-api/array.html#c.PyArray_DescrFromType explicit dtype(int typenum) : object(detail::npy_api::get().PyArray_DescrFromType_(typenum), stolen_t{}) { if (m_ptr == nullptr) { @@ -875,7 +879,7 @@ public: */ template detail::unchecked_mutable_reference mutable_unchecked() & { - if (PYBIND11_SILENCE_MSVC_C4127(Dims >= 0) && ndim() != Dims) { + if (Dims >= 0 && ndim() != Dims) { throw std::domain_error("array has incorrect number of dimensions: " + std::to_string(ndim()) + "; expected " + std::to_string(Dims)); @@ -893,7 +897,7 @@ public: */ template detail::unchecked_reference unchecked() const & { - if (PYBIND11_SILENCE_MSVC_C4127(Dims >= 0) && ndim() != Dims) { + if (Dims >= 0 && ndim() != Dims) { throw std::domain_error("array has incorrect number of dimensions: " + std::to_string(ndim()) + "; expected " + std::to_string(Dims)); @@ -1119,10 +1123,10 @@ public: /** * Returns a proxy object that provides const access to the array's data without bounds or - * dimensionality checking. Unlike `unchecked()`, this does not require that the underlying - * array have the `writable` flag. Use with care: the array must not be destroyed or reshaped - * for the duration of the returned object, and the caller must take care not to access invalid - * dimensions or dimension indices. + * dimensionality checking. Unlike `mutable_unchecked()`, this does not require that the + * underlying array have the `writable` flag. Use with care: the array must not be destroyed + * or reshaped for the duration of the returned object, and the caller must take care not to + * access invalid dimensions or dimension indices. */ template detail::unchecked_reference unchecked() const & { @@ -1281,12 +1285,16 @@ private: public: static constexpr int value = values[detail::is_fmt_numeric::index]; - static pybind11::dtype dtype() { - if (auto *ptr = npy_api::get().PyArray_DescrFromType_(value)) { - return reinterpret_steal(ptr); - } - pybind11_fail("Unsupported buffer format!"); - } + static pybind11::dtype dtype() { return pybind11::dtype(/*typenum*/ value); } +}; + +template +struct npy_format_descriptor::value>> { + static constexpr auto name = const_name("object"); + + static constexpr int value = npy_api::NPY_OBJECT_; + + static pybind11::dtype dtype() { return pybind11::dtype(/*typenum*/ value); } }; #define PYBIND11_DECL_CHAR_FMT \ @@ -1401,7 +1409,7 @@ PYBIND11_NOINLINE void register_structured_dtype(any_container oss << '}'; auto format_str = oss.str(); - // Sanity check: verify that NumPy properly parses our buffer format string + // Smoke test: verify that NumPy properly parses our buffer format string auto &api = npy_api::get(); auto arr = array(buffer_info(nullptr, itemsize, format_str, 1)); if (!api.PyArray_EquivTypes_(dtype_ptr, arr.dtype().ptr())) { @@ -1469,7 +1477,7 @@ private: } // Extract name, offset and format descriptor for a struct field -# define PYBIND11_FIELD_DESCRIPTOR(T, Field) PYBIND11_FIELD_DESCRIPTOR_EX(T, Field, # Field) +# define PYBIND11_FIELD_DESCRIPTOR(T, Field) PYBIND11_FIELD_DESCRIPTOR_EX(T, Field, #Field) // The main idea of this macro is borrowed from https://github.com/swansontec/map-macro // (C) William Swanson, Paul Fultz @@ -1866,8 +1874,13 @@ private: auto result = returned_array::create(trivial, shape); + PYBIND11_WARNING_PUSH +#ifdef PYBIND11_DETECTED_CLANG_WITH_MISLEADING_CALL_STD_MOVE_EXPLICITLY_WARNING + PYBIND11_WARNING_DISABLE_CLANG("-Wreturn-std-move") +#endif + if (size == 0) { - return std::move(result); + return result; } /* Call the function */ @@ -1878,7 +1891,8 @@ private: apply_trivial(buffers, params, mutable_data, size, i_seq, vi_seq, bi_seq); } - return std::move(result); + return result; + PYBIND11_WARNING_POP } template diff --git a/pybind11/include/pybind11/operators.h b/pybind11/include/pybind11/operators.h index a0c3b78d6..16a88ae17 100644 --- a/pybind11/include/pybind11/operators.h +++ b/pybind11/include/pybind11/operators.h @@ -84,6 +84,7 @@ struct op_impl {}; /// Operator implementation generator template struct op_ { + static constexpr bool op_enable_if_hook = true; template void execute(Class &cl, const Extra &...extra) const { using Base = typename Class::type; diff --git a/pybind11/include/pybind11/options.h b/pybind11/include/pybind11/options.h index 1e493bdcc..1b2122522 100644 --- a/pybind11/include/pybind11/options.h +++ b/pybind11/include/pybind11/options.h @@ -47,6 +47,16 @@ public: return *this; } + options &disable_enum_members_docstring() & { + global_state().show_enum_members_docstring = false; + return *this; + } + + options &enable_enum_members_docstring() & { + global_state().show_enum_members_docstring = true; + return *this; + } + // Getter methods (return the global state): static bool show_user_defined_docstrings() { @@ -55,6 +65,10 @@ public: static bool show_function_signatures() { return global_state().show_function_signatures; } + static bool show_enum_members_docstring() { + return global_state().show_enum_members_docstring; + } + // This type is not meant to be allocated on the heap. void *operator new(size_t) = delete; @@ -63,6 +77,8 @@ private: bool show_user_defined_docstrings = true; //< Include user-supplied texts in docstrings. bool show_function_signatures = true; //< Include auto-generated function signatures // in docstrings. + bool show_enum_members_docstring = true; //< Include auto-generated member list in enum + // docstrings. }; static state &global_state() { diff --git a/pybind11/include/pybind11/pybind11.h b/pybind11/include/pybind11/pybind11.h index d61dcd5c7..3bce1a01b 100644 --- a/pybind11/include/pybind11/pybind11.h +++ b/pybind11/include/pybind11/pybind11.h @@ -35,6 +35,8 @@ # include #endif +PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE) + /* https://stackoverflow.com/questions/46798456/handling-gccs-noexcept-type-warning This warning is about ABI compatibility, not code health. It is only actually needed in a couple places, but apparently GCC 7 "generates this warning if @@ -43,11 +45,10 @@ No other GCC version generates this warning. */ #if defined(__GNUC__) && __GNUC__ == 7 -# pragma GCC diagnostic push -# pragma GCC diagnostic ignored "-Wnoexcept-type" +PYBIND11_WARNING_DISABLE_GCC("-Wnoexcept-type") #endif -PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE) +PYBIND11_WARNING_DISABLE_MSVC(4127) PYBIND11_NAMESPACE_BEGIN(detail) @@ -83,6 +84,7 @@ public: cpp_function() = default; // NOLINTNEXTLINE(google-explicit-constructor) cpp_function(std::nullptr_t) {} + cpp_function(std::nullptr_t, const is_setter &) {} /// Construct a cpp_function from a vanilla function pointer template @@ -177,22 +179,22 @@ protected: auto *rec = unique_rec.get(); /* Store the capture object directly in the function record if there is enough space */ - if (PYBIND11_SILENCE_MSVC_C4127(sizeof(capture) <= sizeof(rec->data))) { + if (sizeof(capture) <= sizeof(rec->data)) { /* Without these pragmas, GCC warns that there might not be enough space to use the placement new operator. However, the 'if' statement above ensures that this is the case. */ -#if defined(__GNUG__) && __GNUC__ >= 6 && !defined(__clang__) && !defined(__INTEL_COMPILER) -# pragma GCC diagnostic push -# pragma GCC diagnostic ignored "-Wplacement-new" + PYBIND11_WARNING_PUSH + +#if defined(__GNUG__) && __GNUC__ >= 6 + PYBIND11_WARNING_DISABLE_GCC("-Wplacement-new") #endif + new ((capture *) &rec->data) capture{std::forward(f)}; -#if defined(__GNUG__) && __GNUC__ >= 6 && !defined(__clang__) && !defined(__INTEL_COMPILER) -# pragma GCC diagnostic pop -#endif -#if defined(__GNUG__) && !PYBIND11_HAS_STD_LAUNDER && !defined(__INTEL_COMPILER) -# pragma GCC diagnostic push -# pragma GCC diagnostic ignored "-Wstrict-aliasing" + +#if !PYBIND11_HAS_STD_LAUNDER + PYBIND11_WARNING_DISABLE_GCC("-Wstrict-aliasing") #endif + // UB without std::launder, but without breaking ABI and/or // a significant refactoring it's "impossible" to solve. if (!std::is_trivially_destructible::value) { @@ -202,9 +204,7 @@ protected: data->~capture(); }; } -#if defined(__GNUG__) && !PYBIND11_HAS_STD_LAUNDER && !defined(__INTEL_COMPILER) -# pragma GCC diagnostic pop -#endif + PYBIND11_WARNING_POP } else { rec->data[0] = new capture{std::forward(f)}; rec->free_data = [](function_record *r) { delete ((capture *) r->data[0]); }; @@ -245,10 +245,16 @@ protected: using Guard = extract_guard_t; /* Perform the function call */ - handle result - = cast_out::cast(std::move(args_converter).template call(cap->f), - policy, - call.parent); + handle result; + if (call.func.is_setter) { + (void) std::move(args_converter).template call(cap->f); + result = none().release(); + } else { + result = cast_out::cast( + std::move(args_converter).template call(cap->f), + policy, + call.parent); + } /* Invoke call policy post-call hook */ process_attributes::postcall(call, result); @@ -468,13 +474,20 @@ protected: if (rec->sibling) { if (PyCFunction_Check(rec->sibling.ptr())) { auto *self = PyCFunction_GET_SELF(rec->sibling.ptr()); - capsule rec_capsule = isinstance(self) ? reinterpret_borrow(self) - : capsule(self); - chain = (detail::function_record *) rec_capsule; - /* Never append a method to an overload chain of a parent class; - instead, hide the parent's overloads in this case */ - if (!chain->scope.is(rec->scope)) { + if (!isinstance(self)) { chain = nullptr; + } else { + auto rec_capsule = reinterpret_borrow(self); + if (detail::is_function_record_capsule(rec_capsule)) { + chain = rec_capsule.get_pointer(); + /* Never append a method to an overload chain of a parent class; + instead, hide the parent's overloads in this case */ + if (!chain->scope.is(rec->scope)) { + chain = nullptr; + } + } else { + chain = nullptr; + } } } // Don't trigger for things like the default __init__, which are wrapper_descriptors @@ -495,6 +508,7 @@ protected: rec->def->ml_flags = METH_VARARGS | METH_KEYWORDS; capsule rec_capsule(unique_rec.release(), + detail::get_function_record_capsule_name(), [](void *ptr) { destruct((detail::function_record *) ptr); }); guarded_strdup.release(); @@ -661,10 +675,13 @@ protected: /// Main dispatch logic for calls to functions bound using pybind11 static PyObject *dispatcher(PyObject *self, PyObject *args_in, PyObject *kwargs_in) { using namespace detail; + assert(isinstance(self)); /* Iterator over the list of potentially admissible overloads */ - const function_record *overloads = (function_record *) PyCapsule_GetPointer(self, nullptr), + const function_record *overloads = reinterpret_cast( + PyCapsule_GetPointer(self, get_function_record_capsule_name())), *it = overloads; + assert(overloads != nullptr); /* Need to know how many arguments + keyword arguments there are to pick the right overload */ @@ -1416,9 +1433,9 @@ template ::value, int> = 0> void call_operator_delete(T *p, size_t, size_t) { T::operator delete(p); } -template < - typename T, - enable_if_t::value && has_operator_delete_size::value, int> = 0> +template ::value && has_operator_delete_size::value, int> + = 0> void call_operator_delete(T *p, size_t s, size_t) { T::operator delete(p, s); } @@ -1578,14 +1595,14 @@ public: return *this; } - template - class_ &def(const detail::op_ &op, const Extra &...extra) { + template = 0> + class_ &def(const T &op, const Extra &...extra) { op.execute(*this, extra...); return *this; } - template - class_ &def_cast(const detail::op_ &op, const Extra &...extra) { + template = 0> + class_ &def_cast(const T &op, const Extra &...extra) { op.execute_cast(*this, extra...); return *this; } @@ -1719,7 +1736,8 @@ public: template class_ & def_property(const char *name, const Getter &fget, const Setter &fset, const Extra &...extra) { - return def_property(name, fget, cpp_function(method_adaptor(fset)), extra...); + return def_property( + name, fget, cpp_function(method_adaptor(fset), is_setter()), extra...); } template class_ &def_property(const char *name, @@ -1830,8 +1848,7 @@ private: if (holder_ptr) { init_holder_from_existing(v_h, holder_ptr, std::is_copy_constructible()); v_h.set_holder_constructed(); - } else if (PYBIND11_SILENCE_MSVC_C4127(detail::always_construct_holder::value) - || inst->owned) { + } else if (detail::always_construct_holder::value || inst->owned) { new (std::addressof(v_h.holder())) holder_type(v_h.value_ptr()); v_h.set_holder_constructed(); } @@ -1871,9 +1888,22 @@ private: static detail::function_record *get_function_record(handle h) { h = detail::get_function(h); - return h ? (detail::function_record *) reinterpret_borrow( - PyCFunction_GET_SELF(h.ptr())) - : nullptr; + if (!h) { + return nullptr; + } + + handle func_self = PyCFunction_GET_SELF(h.ptr()); + if (!func_self) { + throw error_already_set(); + } + if (!isinstance(func_self)) { + return nullptr; + } + auto cap = reinterpret_borrow(func_self); + if (!detail::is_function_record_capsule(cap)) { + return nullptr; + } + return cap.get_pointer(); } }; @@ -1950,29 +1980,35 @@ struct enum_base { name("name"), is_method(m_base)); - m_base.attr("__doc__") = static_property( - cpp_function( - [](handle arg) -> std::string { - std::string docstring; - dict entries = arg.attr("__entries"); - if (((PyTypeObject *) arg.ptr())->tp_doc) { - docstring += std::string(((PyTypeObject *) arg.ptr())->tp_doc) + "\n\n"; - } - docstring += "Members:"; - for (auto kv : entries) { - auto key = std::string(pybind11::str(kv.first)); - auto comment = kv.second[int_(1)]; - docstring += "\n\n " + key; - if (!comment.is_none()) { - docstring += " : " + (std::string) pybind11::str(comment); + if (options::show_enum_members_docstring()) { + m_base.attr("__doc__") = static_property( + cpp_function( + [](handle arg) -> std::string { + std::string docstring; + dict entries = arg.attr("__entries"); + if (((PyTypeObject *) arg.ptr())->tp_doc) { + docstring += std::string( + reinterpret_cast(arg.ptr())->tp_doc); + docstring += "\n\n"; } - } - return docstring; - }, - name("__doc__")), - none(), - none(), - ""); + docstring += "Members:"; + for (auto kv : entries) { + auto key = std::string(pybind11::str(kv.first)); + auto comment = kv.second[int_(1)]; + docstring += "\n\n "; + docstring += key; + if (!comment.is_none()) { + docstring += " : "; + docstring += pybind11::str(comment).cast(); + } + } + return docstring; + }, + name("__doc__")), + none(), + none(), + ""); + } m_base.attr("__members__") = static_property(cpp_function( [](handle arg) -> dict { @@ -2073,7 +2109,7 @@ struct enum_base { + "\" already exists!"); } - entries[name] = std::make_pair(value, doc); + entries[name] = pybind11::make_tuple(value, doc); m_base.attr(std::move(name)) = std::move(value); } @@ -2333,7 +2369,7 @@ template -iterator make_iterator_impl(Iterator &&first, Sentinel &&last, Extra &&...extra) { +iterator make_iterator_impl(Iterator first, Sentinel last, Extra &&...extra) { using state = detail::iterator_state; // TODO: state captures only the types of Extra, not the values @@ -2359,7 +2395,7 @@ iterator make_iterator_impl(Iterator &&first, Sentinel &&last, Extra &&...extra) Policy); } - return cast(state{std::forward(first), std::forward(last), true}); + return cast(state{first, last, true}); } PYBIND11_NAMESPACE_END(detail) @@ -2370,15 +2406,13 @@ template ::result_type, typename... Extra> -iterator make_iterator(Iterator &&first, Sentinel &&last, Extra &&...extra) { +iterator make_iterator(Iterator first, Sentinel last, Extra &&...extra) { return detail::make_iterator_impl, Policy, Iterator, Sentinel, ValueType, - Extra...>(std::forward(first), - std::forward(last), - std::forward(extra)...); + Extra...>(first, last, std::forward(extra)...); } /// Makes a python iterator over the keys (`.first`) of a iterator over pairs from a @@ -2388,15 +2422,13 @@ template ::result_type, typename... Extra> -iterator make_key_iterator(Iterator &&first, Sentinel &&last, Extra &&...extra) { +iterator make_key_iterator(Iterator first, Sentinel last, Extra &&...extra) { return detail::make_iterator_impl, Policy, Iterator, Sentinel, KeyType, - Extra...>(std::forward(first), - std::forward(last), - std::forward(extra)...); + Extra...>(first, last, std::forward(extra)...); } /// Makes a python iterator over the values (`.second`) of a iterator over pairs from a @@ -2406,15 +2438,13 @@ template ::result_type, typename... Extra> -iterator make_value_iterator(Iterator &&first, Sentinel &&last, Extra &&...extra) { +iterator make_value_iterator(Iterator first, Sentinel last, Extra &&...extra) { return detail::make_iterator_impl, Policy, Iterator, Sentinel, ValueType, - Extra...>(std::forward(first), - std::forward(last), - std::forward(extra)...); + Extra...>(first, last, std::forward(extra)...); } /// Makes an iterator over values of an stl container or other container supporting @@ -2858,7 +2888,3 @@ inline function get_overload(const T *this_ptr, const char *name) { PYBIND11_OVERRIDE_PURE(PYBIND11_TYPE(ret_type), PYBIND11_TYPE(cname), fn, __VA_ARGS__); PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE) - -#if defined(__GNUC__) && __GNUC__ == 7 -# pragma GCC diagnostic pop // -Wnoexcept-type -#endif diff --git a/pybind11/include/pybind11/pytypes.h b/pybind11/include/pybind11/pytypes.h index 339b0961e..64aad6347 100644 --- a/pybind11/include/pybind11/pytypes.h +++ b/pybind11/include/pybind11/pytypes.h @@ -33,6 +33,8 @@ PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE) +PYBIND11_WARNING_DISABLE_MSVC(4127) + /* A few forward declarations */ class handle; class object; @@ -155,23 +157,23 @@ public: object operator-() const; object operator~() const; object operator+(object_api const &other) const; - object operator+=(object_api const &other) const; + object operator+=(object_api const &other); object operator-(object_api const &other) const; - object operator-=(object_api const &other) const; + object operator-=(object_api const &other); object operator*(object_api const &other) const; - object operator*=(object_api const &other) const; + object operator*=(object_api const &other); object operator/(object_api const &other) const; - object operator/=(object_api const &other) const; + object operator/=(object_api const &other); object operator|(object_api const &other) const; - object operator|=(object_api const &other) const; + object operator|=(object_api const &other); object operator&(object_api const &other) const; - object operator&=(object_api const &other) const; + object operator&=(object_api const &other); object operator^(object_api const &other) const; - object operator^=(object_api const &other) const; + object operator^=(object_api const &other); object operator<<(object_api const &other) const; - object operator<<=(object_api const &other) const; + object operator<<=(object_api const &other); object operator>>(object_api const &other) const; - object operator>>=(object_api const &other) const; + object operator>>=(object_api const &other); PYBIND11_DEPRECATED("Use py::str(obj) instead") pybind11::str str() const; @@ -230,7 +232,8 @@ public: detail::enable_if_t, detail::is_pyobj_ptr_or_nullptr_t>, std::is_convertible>::value, - int> = 0> + int> + = 0> // NOLINTNEXTLINE(google-explicit-constructor) handle(T &obj) : m_ptr(obj) {} @@ -246,6 +249,11 @@ public: const handle &inc_ref() const & { #ifdef PYBIND11_HANDLE_REF_DEBUG inc_ref_counter(1); +#endif +#ifdef PYBIND11_ASSERT_GIL_HELD_INCREF_DECREF + if (m_ptr != nullptr && !PyGILState_Check()) { + throw_gilstate_error("pybind11::handle::inc_ref()"); + } #endif Py_XINCREF(m_ptr); return *this; @@ -257,6 +265,11 @@ public: this function automatically. Returns a reference to itself. \endrst */ const handle &dec_ref() const & { +#ifdef PYBIND11_ASSERT_GIL_HELD_INCREF_DECREF + if (m_ptr != nullptr && !PyGILState_Check()) { + throw_gilstate_error("pybind11::handle::dec_ref()"); + } +#endif Py_XDECREF(m_ptr); return *this; } @@ -283,8 +296,33 @@ public: protected: PyObject *m_ptr = nullptr; -#ifdef PYBIND11_HANDLE_REF_DEBUG private: +#ifdef PYBIND11_ASSERT_GIL_HELD_INCREF_DECREF + void throw_gilstate_error(const std::string &function_name) const { + fprintf( + stderr, + "%s is being called while the GIL is either not held or invalid. Please see " + "https://pybind11.readthedocs.io/en/stable/advanced/" + "misc.html#common-sources-of-global-interpreter-lock-errors for debugging advice.\n" + "If you are convinced there is no bug in your code, you can #define " + "PYBIND11_NO_ASSERT_GIL_HELD_INCREF_DECREF" + "to disable this check. In that case you have to ensure this #define is consistently " + "used for all translation units linked into a given pybind11 extension, otherwise " + "there will be ODR violations.", + function_name.c_str()); + fflush(stderr); + if (Py_TYPE(m_ptr)->tp_name != nullptr) { + fprintf(stderr, + "The failing %s call was triggered on a %s object.\n", + function_name.c_str(), + Py_TYPE(m_ptr)->tp_name); + fflush(stderr); + } + throw std::runtime_error(function_name + " PyGILState_Check() failure."); + } +#endif + +#ifdef PYBIND11_HANDLE_REF_DEBUG static std::size_t inc_ref_counter(std::size_t add) { thread_local std::size_t counter = 0; counter += add; @@ -334,12 +372,15 @@ public: } object &operator=(const object &other) { - other.inc_ref(); - // Use temporary variable to ensure `*this` remains valid while - // `Py_XDECREF` executes, in case `*this` is accessible from Python. - handle temp(m_ptr); - m_ptr = other.m_ptr; - temp.dec_ref(); + // Skip inc_ref and dec_ref if both objects are the same + if (!this->is(other)) { + other.inc_ref(); + // Use temporary variable to ensure `*this` remains valid while + // `Py_XDECREF` executes, in case `*this` is accessible from Python. + handle temp(m_ptr); + m_ptr = other.m_ptr; + temp.dec_ref(); + } return *this; } @@ -353,6 +394,20 @@ public: return *this; } +#define PYBIND11_INPLACE_OP(iop) \ + object iop(object_api const &other) { return operator=(handle::iop(other)); } + + PYBIND11_INPLACE_OP(operator+=) + PYBIND11_INPLACE_OP(operator-=) + PYBIND11_INPLACE_OP(operator*=) + PYBIND11_INPLACE_OP(operator/=) + PYBIND11_INPLACE_OP(operator|=) + PYBIND11_INPLACE_OP(operator&=) + PYBIND11_INPLACE_OP(operator^=) + PYBIND11_INPLACE_OP(operator<<=) + PYBIND11_INPLACE_OP(operator>>=) +#undef PYBIND11_INPLACE_OP + // Calling cast() on an object lvalue just copies (via handle::cast) template T cast() const &; @@ -413,7 +468,7 @@ PYBIND11_NAMESPACE_BEGIN(detail) // Equivalent to obj.__class__.__name__ (or obj.__name__ if obj is a class). inline const char *obj_class_name(PyObject *obj) { - if (Py_TYPE(obj) == &PyType_Type) { + if (PyType_Check(obj)) { return reinterpret_cast(obj)->tp_name; } return Py_TYPE(obj)->tp_name; @@ -421,13 +476,24 @@ inline const char *obj_class_name(PyObject *obj) { std::string error_string(); +// The code in this struct is very unusual, to minimize the chances of +// masking bugs (elsewhere) by errors during the error handling (here). +// This is meant to be a lifeline for troubleshooting long-running processes +// that crash under conditions that are virtually impossible to reproduce. +// Low-level implementation alternatives are preferred to higher-level ones +// that might raise cascading exceptions. Last-ditch-kind-of attempts are made +// to report as much of the original error as possible, even if there are +// secondary issues obtaining some of the details. struct error_fetch_and_normalize { - // Immediate normalization is long-established behavior (starting with - // https://github.com/pybind/pybind11/commit/135ba8deafb8bf64a15b24d1513899eb600e2011 - // from Sep 2016) and safest. Normalization could be deferred, but this could mask - // errors elsewhere, the performance gain is very minor in typical situations - // (usually the dominant bottleneck is EH unwinding), and the implementation here - // would be more complex. + // This comment only applies to Python <= 3.11: + // Immediate normalization is long-established behavior (starting with + // https://github.com/pybind/pybind11/commit/135ba8deafb8bf64a15b24d1513899eb600e2011 + // from Sep 2016) and safest. Normalization could be deferred, but this could mask + // errors elsewhere, the performance gain is very minor in typical situations + // (usually the dominant bottleneck is EH unwinding), and the implementation here + // would be more complex. + // Starting with Python 3.12, PyErr_Fetch() normalizes exceptions immediately. + // Any errors during normalization are tracked under __notes__. explicit error_fetch_and_normalize(const char *called) { PyErr_Fetch(&m_type.ptr(), &m_value.ptr(), &m_trace.ptr()); if (!m_type) { @@ -442,6 +508,14 @@ struct error_fetch_and_normalize { "of the original active exception type."); } m_lazy_error_string = exc_type_name_orig; +#if PY_VERSION_HEX >= 0x030C0000 + // The presence of __notes__ is likely due to exception normalization + // errors, although that is not necessarily true, therefore insert a + // hint only: + if (PyObject_HasAttrString(m_value.ptr(), "__notes__")) { + m_lazy_error_string += "[WITH __notes__]"; + } +#else // PyErr_NormalizeException() may change the exception type if there are cascading // failures. This can potentially be extremely confusing. PyErr_NormalizeException(&m_type.ptr(), &m_value.ptr(), &m_trace.ptr()); @@ -451,11 +525,17 @@ struct error_fetch_and_normalize { "active exception."); } const char *exc_type_name_norm = detail::obj_class_name(m_type.ptr()); - if (exc_type_name_orig == nullptr) { + if (exc_type_name_norm == nullptr) { pybind11_fail("Internal error: " + std::string(called) + " failed to obtain the name " "of the normalized active exception type."); } +# if defined(PYPY_VERSION_NUM) && PYPY_VERSION_NUM < 0x07030a00 + // This behavior runs the risk of masking errors in the error handling, but avoids a + // conflict with PyPy, which relies on the normalization here to change OSError to + // FileNotFoundError (https://github.com/pybind/pybind11/issues/4075). + m_lazy_error_string = exc_type_name_norm; +# else if (exc_type_name_norm != m_lazy_error_string) { std::string msg = std::string(called) + ": MISMATCH of original and normalized " @@ -467,6 +547,8 @@ struct error_fetch_and_normalize { msg += ": " + format_value_and_trace(); pybind11_fail(msg); } +# endif +#endif } error_fetch_and_normalize(const error_fetch_and_normalize &) = delete; @@ -477,12 +559,64 @@ struct error_fetch_and_normalize { std::string message_error_string; if (m_value) { auto value_str = reinterpret_steal(PyObject_Str(m_value.ptr())); + constexpr const char *message_unavailable_exc + = ""; if (!value_str) { message_error_string = detail::error_string(); - result = ""; + result = message_unavailable_exc; } else { - result = value_str.cast(); + // Not using `value_str.cast()`, to not potentially throw a secondary + // error_already_set that will then result in process termination (#4288). + auto value_bytes = reinterpret_steal( + PyUnicode_AsEncodedString(value_str.ptr(), "utf-8", "backslashreplace")); + if (!value_bytes) { + message_error_string = detail::error_string(); + result = message_unavailable_exc; + } else { + char *buffer = nullptr; + Py_ssize_t length = 0; + if (PyBytes_AsStringAndSize(value_bytes.ptr(), &buffer, &length) == -1) { + message_error_string = detail::error_string(); + result = message_unavailable_exc; + } else { + result = std::string(buffer, static_cast(length)); + } + } } +#if PY_VERSION_HEX >= 0x030B0000 + auto notes + = reinterpret_steal(PyObject_GetAttrString(m_value.ptr(), "__notes__")); + if (!notes) { + PyErr_Clear(); // No notes is good news. + } else { + auto len_notes = PyList_Size(notes.ptr()); + if (len_notes < 0) { + result += "\nFAILURE obtaining len(__notes__): " + detail::error_string(); + } else { + result += "\n__notes__ (len=" + std::to_string(len_notes) + "):"; + for (ssize_t i = 0; i < len_notes; i++) { + PyObject *note = PyList_GET_ITEM(notes.ptr(), i); + auto note_bytes = reinterpret_steal( + PyUnicode_AsEncodedString(note, "utf-8", "backslashreplace")); + if (!note_bytes) { + result += "\nFAILURE obtaining __notes__[" + std::to_string(i) + + "]: " + detail::error_string(); + } else { + char *buffer = nullptr; + Py_ssize_t length = 0; + if (PyBytes_AsStringAndSize(note_bytes.ptr(), &buffer, &length) + == -1) { + result += "\nFAILURE formatting __notes__[" + std::to_string(i) + + "]: " + detail::error_string(); + } else { + result += '\n'; + result += std::string(buffer, static_cast(length)); + } + } + } + } + } +#endif } else { result = ""; } @@ -581,12 +715,6 @@ inline std::string error_string() { PYBIND11_NAMESPACE_END(detail) -#if defined(_MSC_VER) -# pragma warning(push) -# pragma warning(disable : 4275 4251) -// warning C4275: An exported class was derived from a class that wasn't exported. -// Can be ignored when derived from a STL class. -#endif /// Fetch and hold an error which was already set in Python. An instance of this is typically /// thrown to propagate python-side errors back through C++ which can either be caught manually or /// else falls back to the function dispatcher (which then raises the captured error back to @@ -646,9 +774,6 @@ private: /// crashes (undefined behavior) if the Python interpreter is finalizing. static void m_fetched_error_deleter(detail::error_fetch_and_normalize *raw_ptr); }; -#if defined(_MSC_VER) -# pragma warning(pop) -#endif /// Replaces the current Python error indicator with the chosen error, performing a /// 'raise from' to indicate that the chosen error was caused by the original error. @@ -851,10 +976,8 @@ object object_or_cast(T &&o); // Match a PyObject*, which we want to convert directly to handle via its converting constructor inline handle object_or_cast(PyObject *ptr) { return ptr; } -#if defined(_MSC_VER) && _MSC_VER < 1920 -# pragma warning(push) -# pragma warning(disable : 4522) // warning C4522: multiple assignment operators specified -#endif +PYBIND11_WARNING_PUSH +PYBIND11_WARNING_DISABLE_MSVC(4522) // warning C4522: multiple assignment operators specified template class accessor : public object_api> { using key_type = typename Policy::key_type; @@ -918,9 +1041,7 @@ private: key_type key; mutable object cache; }; -#if defined(_MSC_VER) && _MSC_VER < 1920 -# pragma warning(pop) -#endif +PYBIND11_WARNING_POP PYBIND11_NAMESPACE_BEGIN(accessor_policies) struct obj_attr { @@ -1266,7 +1387,7 @@ public: #define PYBIND11_OBJECT_CVT_DEFAULT(Name, Parent, CheckFun, ConvertFun) \ PYBIND11_OBJECT_CVT(Name, Parent, CheckFun, ConvertFun) \ - Name() : Parent() {} + Name() = default; #define PYBIND11_OBJECT_CHECK_FAILED(Name, o_ptr) \ ::pybind11::type_error("Object of type '" \ @@ -1289,7 +1410,7 @@ public: #define PYBIND11_OBJECT_DEFAULT(Name, Parent, CheckFun) \ PYBIND11_OBJECT(Name, Parent, CheckFun) \ - Name() : Parent() {} + Name() = default; /// \addtogroup pytypes /// @{ @@ -1358,7 +1479,7 @@ public: private: void advance() { value = reinterpret_steal(PyIter_Next(m_ptr)); - if (PyErr_Occurred()) { + if (value.ptr() == nullptr && PyErr_Occurred()) { throw error_already_set(); } } @@ -1408,6 +1529,9 @@ public: str(const char *c, const SzType &n) : object(PyUnicode_FromStringAndSize(c, ssize_t_cast(n)), stolen_t{}) { if (!m_ptr) { + if (PyErr_Occurred()) { + throw error_already_set(); + } pybind11_fail("Could not allocate string object!"); } } @@ -1417,6 +1541,9 @@ public: // NOLINTNEXTLINE(google-explicit-constructor) str(const char *c = "") : object(PyUnicode_FromString(c), stolen_t{}) { if (!m_ptr) { + if (PyErr_Occurred()) { + throw error_already_set(); + } pybind11_fail("Could not allocate string object!"); } } @@ -1574,6 +1701,9 @@ inline str::str(const bytes &b) { } auto obj = reinterpret_steal(PyUnicode_FromStringAndSize(buffer, length)); if (!obj) { + if (PyErr_Occurred()) { + throw error_already_set(); + } pybind11_fail("Could not allocate string object!"); } m_ptr = obj.release().ptr(); @@ -1651,7 +1781,7 @@ PYBIND11_NAMESPACE_BEGIN(detail) // unsigned type: (A)-1 != (B)-1 when A and B are unsigned types of different sizes). template Unsigned as_unsigned(PyObject *o) { - if (PYBIND11_SILENCE_MSVC_C4127(sizeof(Unsigned) <= sizeof(unsigned long))) { + if (sizeof(Unsigned) <= sizeof(unsigned long)) { unsigned long v = PyLong_AsUnsignedLong(o); return v == (unsigned long) -1 && PyErr_Occurred() ? (Unsigned) -1 : (Unsigned) v; } @@ -1668,7 +1798,7 @@ public: template ::value, int> = 0> // NOLINTNEXTLINE(google-explicit-constructor) int_(T value) { - if (PYBIND11_SILENCE_MSVC_C4127(sizeof(T) <= sizeof(long))) { + if (sizeof(T) <= sizeof(long)) { if (std::is_signed::value) { m_ptr = PyLong_FromLong((long) value); } else { @@ -1785,43 +1915,28 @@ public: explicit capsule(const void *value, const char *name = nullptr, - void (*destructor)(PyObject *) = nullptr) + PyCapsule_Destructor destructor = nullptr) : object(PyCapsule_New(const_cast(value), name, destructor), stolen_t{}) { if (!m_ptr) { throw error_already_set(); } } - PYBIND11_DEPRECATED("Please pass a destructor that takes a void pointer as input") - capsule(const void *value, void (*destruct)(PyObject *)) - : object(PyCapsule_New(const_cast(value), nullptr, destruct), stolen_t{}) { + PYBIND11_DEPRECATED("Please use the ctor with value, name, destructor args") + capsule(const void *value, PyCapsule_Destructor destructor) + : object(PyCapsule_New(const_cast(value), nullptr, destructor), stolen_t{}) { if (!m_ptr) { throw error_already_set(); } } + /// Capsule name is nullptr. capsule(const void *value, void (*destructor)(void *)) { - m_ptr = PyCapsule_New(const_cast(value), nullptr, [](PyObject *o) { - // guard if destructor called while err indicator is set - error_scope error_guard; - auto destructor = reinterpret_cast(PyCapsule_GetContext(o)); - if (destructor == nullptr) { - if (PyErr_Occurred()) { - throw error_already_set(); - } - pybind11_fail("Unable to get capsule context"); - } - const char *name = get_name_in_error_scope(o); - void *ptr = PyCapsule_GetPointer(o, name); - if (ptr == nullptr) { - throw error_already_set(); - } - destructor(ptr); - }); + initialize_with_void_ptr_destructor(value, nullptr, destructor); + } - if (!m_ptr || PyCapsule_SetContext(m_ptr, (void *) destructor) != 0) { - throw error_already_set(); - } + capsule(const void *value, const char *name, void (*destructor)(void *)) { + initialize_with_void_ptr_destructor(value, name, destructor); } explicit capsule(void (*destructor)()) { @@ -1889,6 +2004,32 @@ private: return name; } + + void initialize_with_void_ptr_destructor(const void *value, + const char *name, + void (*destructor)(void *)) { + m_ptr = PyCapsule_New(const_cast(value), name, [](PyObject *o) { + // guard if destructor called while err indicator is set + error_scope error_guard; + auto destructor = reinterpret_cast(PyCapsule_GetContext(o)); + if (destructor == nullptr && PyErr_Occurred()) { + throw error_already_set(); + } + const char *name = get_name_in_error_scope(o); + void *ptr = PyCapsule_GetPointer(o, name); + if (ptr == nullptr) { + throw error_already_set(); + } + + if (destructor != nullptr) { + destructor(ptr); + } + }); + + if (!m_ptr || PyCapsule_SetContext(m_ptr, reinterpret_cast(destructor)) != 0) { + throw error_already_set(); + } + } }; class tuple : public object { @@ -1943,7 +2084,11 @@ public: void clear() /* py-non-const */ { PyDict_Clear(ptr()); } template bool contains(T &&key) const { - return PyDict_Contains(m_ptr, detail::object_or_cast(std::forward(key)).ptr()) == 1; + auto result = PyDict_Contains(m_ptr, detail::object_or_cast(std::forward(key)).ptr()); + if (result == -1) { + throw error_already_set(); + } + return result == 1; } private: @@ -1998,14 +2143,20 @@ public: detail::list_iterator end() const { return {*this, PyList_GET_SIZE(m_ptr)}; } template void append(T &&val) /* py-non-const */ { - PyList_Append(m_ptr, detail::object_or_cast(std::forward(val)).ptr()); + if (PyList_Append(m_ptr, detail::object_or_cast(std::forward(val)).ptr()) != 0) { + throw error_already_set(); + } } template ::value, int> = 0> void insert(const IdxType &index, ValType &&val) /* py-non-const */ { - PyList_Insert( - m_ptr, ssize_t_cast(index), detail::object_or_cast(std::forward(val)).ptr()); + if (PyList_Insert(m_ptr, + ssize_t_cast(index), + detail::object_or_cast(std::forward(val)).ptr()) + != 0) { + throw error_already_set(); + } } }; @@ -2023,7 +2174,11 @@ public: bool empty() const { return size() == 0; } template bool contains(T &&val) const { - return PySet_Contains(m_ptr, detail::object_or_cast(std::forward(val)).ptr()) == 1; + auto result = PySet_Contains(m_ptr, detail::object_or_cast(std::forward(val)).ptr()); + if (result == -1) { + throw error_already_set(); + } + return result == 1; } }; @@ -2364,29 +2519,39 @@ bool object_api::rich_compare(object_api const &other, int value) const { return result; \ } +#define PYBIND11_MATH_OPERATOR_BINARY_INPLACE(iop, fn) \ + template \ + object object_api::iop(object_api const &other) { \ + object result = reinterpret_steal(fn(derived().ptr(), other.derived().ptr())); \ + if (!result.ptr()) \ + throw error_already_set(); \ + return result; \ + } + PYBIND11_MATH_OPERATOR_UNARY(operator~, PyNumber_Invert) PYBIND11_MATH_OPERATOR_UNARY(operator-, PyNumber_Negative) PYBIND11_MATH_OPERATOR_BINARY(operator+, PyNumber_Add) -PYBIND11_MATH_OPERATOR_BINARY(operator+=, PyNumber_InPlaceAdd) +PYBIND11_MATH_OPERATOR_BINARY_INPLACE(operator+=, PyNumber_InPlaceAdd) PYBIND11_MATH_OPERATOR_BINARY(operator-, PyNumber_Subtract) -PYBIND11_MATH_OPERATOR_BINARY(operator-=, PyNumber_InPlaceSubtract) +PYBIND11_MATH_OPERATOR_BINARY_INPLACE(operator-=, PyNumber_InPlaceSubtract) PYBIND11_MATH_OPERATOR_BINARY(operator*, PyNumber_Multiply) -PYBIND11_MATH_OPERATOR_BINARY(operator*=, PyNumber_InPlaceMultiply) +PYBIND11_MATH_OPERATOR_BINARY_INPLACE(operator*=, PyNumber_InPlaceMultiply) PYBIND11_MATH_OPERATOR_BINARY(operator/, PyNumber_TrueDivide) -PYBIND11_MATH_OPERATOR_BINARY(operator/=, PyNumber_InPlaceTrueDivide) +PYBIND11_MATH_OPERATOR_BINARY_INPLACE(operator/=, PyNumber_InPlaceTrueDivide) PYBIND11_MATH_OPERATOR_BINARY(operator|, PyNumber_Or) -PYBIND11_MATH_OPERATOR_BINARY(operator|=, PyNumber_InPlaceOr) +PYBIND11_MATH_OPERATOR_BINARY_INPLACE(operator|=, PyNumber_InPlaceOr) PYBIND11_MATH_OPERATOR_BINARY(operator&, PyNumber_And) -PYBIND11_MATH_OPERATOR_BINARY(operator&=, PyNumber_InPlaceAnd) +PYBIND11_MATH_OPERATOR_BINARY_INPLACE(operator&=, PyNumber_InPlaceAnd) PYBIND11_MATH_OPERATOR_BINARY(operator^, PyNumber_Xor) -PYBIND11_MATH_OPERATOR_BINARY(operator^=, PyNumber_InPlaceXor) +PYBIND11_MATH_OPERATOR_BINARY_INPLACE(operator^=, PyNumber_InPlaceXor) PYBIND11_MATH_OPERATOR_BINARY(operator<<, PyNumber_Lshift) -PYBIND11_MATH_OPERATOR_BINARY(operator<<=, PyNumber_InPlaceLshift) +PYBIND11_MATH_OPERATOR_BINARY_INPLACE(operator<<=, PyNumber_InPlaceLshift) PYBIND11_MATH_OPERATOR_BINARY(operator>>, PyNumber_Rshift) -PYBIND11_MATH_OPERATOR_BINARY(operator>>=, PyNumber_InPlaceRshift) +PYBIND11_MATH_OPERATOR_BINARY_INPLACE(operator>>=, PyNumber_InPlaceRshift) #undef PYBIND11_MATH_OPERATOR_UNARY #undef PYBIND11_MATH_OPERATOR_BINARY +#undef PYBIND11_MATH_OPERATOR_BINARY_INPLACE PYBIND11_NAMESPACE_END(detail) PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE) diff --git a/pybind11/include/pybind11/stl.h b/pybind11/include/pybind11/stl.h index ab30ecac0..f39f44f7c 100644 --- a/pybind11/include/pybind11/stl.h +++ b/pybind11/include/pybind11/stl.h @@ -45,21 +45,35 @@ using forwarded_type = conditional_t::value, /// Forwards a value U as rvalue or lvalue according to whether T is rvalue or lvalue; typically /// used for forwarding a container's elements. template -forwarded_type forward_like(U &&u) { +constexpr forwarded_type forward_like(U &&u) { return std::forward>(std::forward(u)); } +// Checks if a container has a STL style reserve method. +// This will only return true for a `reserve()` with a `void` return. +template +using has_reserve_method = std::is_same().reserve(0)), void>; + template struct set_caster { using type = Type; using key_conv = make_caster; +private: + template ::value, int> = 0> + void reserve_maybe(const anyset &s, Type *) { + value.reserve(s.size()); + } + void reserve_maybe(const anyset &, void *) {} + +public: bool load(handle src, bool convert) { if (!isinstance(src)) { return false; } auto s = reinterpret_borrow(src); value.clear(); + reserve_maybe(s, &value); for (auto entry : s) { key_conv conv; if (!conv.load(entry, convert)) { @@ -78,7 +92,7 @@ struct set_caster { pybind11::set s; for (auto &&value : src) { auto value_ = reinterpret_steal( - key_conv::cast(forward_like(value), policy, parent)); + key_conv::cast(detail::forward_like(value), policy, parent)); if (!value_ || !s.add(std::move(value_))) { return handle(); } @@ -94,12 +108,21 @@ struct map_caster { using key_conv = make_caster; using value_conv = make_caster; +private: + template ::value, int> = 0> + void reserve_maybe(const dict &d, Type *) { + value.reserve(d.size()); + } + void reserve_maybe(const dict &, void *) {} + +public: bool load(handle src, bool convert) { if (!isinstance(src)) { return false; } auto d = reinterpret_borrow(src); value.clear(); + reserve_maybe(d, &value); for (auto it : d) { key_conv kconv; value_conv vconv; @@ -122,9 +145,9 @@ struct map_caster { } for (auto &&kv : src) { auto key = reinterpret_steal( - key_conv::cast(forward_like(kv.first), policy_key, parent)); + key_conv::cast(detail::forward_like(kv.first), policy_key, parent)); auto value = reinterpret_steal( - value_conv::cast(forward_like(kv.second), policy_value, parent)); + value_conv::cast(detail::forward_like(kv.second), policy_value, parent)); if (!key || !value) { return handle(); } @@ -160,9 +183,7 @@ struct list_caster { } private: - template < - typename T = Type, - enable_if_t().reserve(0)), void>::value, int> = 0> + template ::value, int> = 0> void reserve_maybe(const sequence &s, Type *) { value.reserve(s.size()); } @@ -178,7 +199,7 @@ public: ssize_t index = 0; for (auto &&value : src) { auto value_ = reinterpret_steal( - value_conv::cast(forward_like(value), policy, parent)); + value_conv::cast(detail::forward_like(value), policy, parent)); if (!value_) { return handle(); } @@ -242,7 +263,7 @@ public: ssize_t index = 0; for (auto &&value : src) { auto value_ = reinterpret_steal( - value_conv::cast(forward_like(value), policy, parent)); + value_conv::cast(detail::forward_like(value), policy, parent)); if (!value_) { return handle(); } @@ -252,11 +273,11 @@ public: } PYBIND11_TYPE_CASTER(ArrayType, - const_name("List[") + value_conv::name + const_name(const_name(""), const_name("Annotated[")) + + const_name("List[") + value_conv::name + const_name("]") + const_name(const_name(""), - const_name("[") + const_name() - + const_name("]")) - + const_name("]")); + const_name(", FixedSize(") + + const_name() + const_name(")]"))); }; template @@ -290,11 +311,12 @@ struct optional_caster { template static handle cast(T &&src, return_value_policy policy, handle parent) { if (!src) { - return none().inc_ref(); + return none().release(); } if (!std::is_lvalue_reference::value) { policy = return_value_policy_override::policy(policy); } + // NOLINTNEXTLINE(bugprone-unchecked-optional-access) return value_conv::cast(*std::forward(src), policy, parent); } diff --git a/pybind11/include/pybind11/stl_bind.h b/pybind11/include/pybind11/stl_bind.h index 22a29b476..49f1b7782 100644 --- a/pybind11/include/pybind11/stl_bind.h +++ b/pybind11/include/pybind11/stl_bind.h @@ -10,10 +10,13 @@ #pragma once #include "detail/common.h" +#include "detail/type_caster_base.h" +#include "cast.h" #include "operators.h" #include #include +#include PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE) PYBIND11_NAMESPACE_BEGIN(detail) @@ -58,9 +61,11 @@ struct is_comparable< /* For a vector/map data structure, recursively check the value type (which is std::pair for maps) */ template -struct is_comparable::is_vector>> { - static constexpr const bool value = is_comparable::value; -}; +struct is_comparable::is_vector>> + : is_comparable::type_to_check_recursively> {}; + +template <> +struct is_comparable : std::true_type {}; /* For pairs, recursively check the two data types */ template @@ -352,13 +357,17 @@ void vector_accessor(enable_if_t::value, Class_> &cl) using DiffType = typename Vector::difference_type; using ItType = typename Vector::iterator; cl.def("__getitem__", [](const Vector &v, DiffType i) -> T { - if (i < 0 && (i += v.size()) < 0) { + if (i < 0) { + i += v.size(); + if (i < 0) { + throw index_error(); + } + } + auto i_st = static_cast(i); + if (i_st >= v.size()) { throw index_error(); } - if ((SizeType) i >= v.size()) { - throw index_error(); - } - return v[(SizeType) i]; + return v[i_st]; }); cl.def( @@ -636,18 +645,52 @@ auto map_if_insertion_operator(Class_ &cl, std::string const &name) "Return the canonical string representation of this map."); } -template +template struct keys_view { - Map ↦ + virtual size_t len() = 0; + virtual iterator iter() = 0; + virtual bool contains(const KeyType &k) = 0; + virtual bool contains(const object &k) = 0; + virtual ~keys_view() = default; }; -template +template struct values_view { + virtual size_t len() = 0; + virtual iterator iter() = 0; + virtual ~values_view() = default; +}; + +template +struct items_view { + virtual size_t len() = 0; + virtual iterator iter() = 0; + virtual ~items_view() = default; +}; + +template +struct KeysViewImpl : public KeysView { + explicit KeysViewImpl(Map &map) : map(map) {} + size_t len() override { return map.size(); } + iterator iter() override { return make_key_iterator(map.begin(), map.end()); } + bool contains(const typename Map::key_type &k) override { return map.find(k) != map.end(); } + bool contains(const object &) override { return false; } Map ↦ }; -template -struct items_view { +template +struct ValuesViewImpl : public ValuesView { + explicit ValuesViewImpl(Map &map) : map(map) {} + size_t len() override { return map.size(); } + iterator iter() override { return make_value_iterator(map.begin(), map.end()); } + Map ↦ +}; + +template +struct ItemsViewImpl : public ItemsView { + explicit ItemsViewImpl(Map &map) : map(map) {} + size_t len() override { return map.size(); } + iterator iter() override { return make_iterator(map.begin(), map.end()); } Map ↦ }; @@ -657,9 +700,11 @@ template , typename... class_ bind_map(handle scope, const std::string &name, Args &&...args) { using KeyType = typename Map::key_type; using MappedType = typename Map::mapped_type; - using KeysView = detail::keys_view; - using ValuesView = detail::values_view; - using ItemsView = detail::items_view; + using StrippedKeyType = detail::remove_cvref_t; + using StrippedMappedType = detail::remove_cvref_t; + using KeysView = detail::keys_view; + using ValuesView = detail::values_view; + using ItemsView = detail::items_view; using Class_ = class_; // If either type is a non-module-local bound type then make the map binding non-local as well; @@ -673,12 +718,57 @@ class_ bind_map(handle scope, const std::string &name, Args && } Class_ cl(scope, name.c_str(), pybind11::module_local(local), std::forward(args)...); - class_ keys_view( - scope, ("KeysView[" + name + "]").c_str(), pybind11::module_local(local)); - class_ values_view( - scope, ("ValuesView[" + name + "]").c_str(), pybind11::module_local(local)); - class_ items_view( - scope, ("ItemsView[" + name + "]").c_str(), pybind11::module_local(local)); + static constexpr auto key_type_descr = detail::make_caster::name; + static constexpr auto mapped_type_descr = detail::make_caster::name; + std::string key_type_name(key_type_descr.text), mapped_type_name(mapped_type_descr.text); + + // If key type isn't properly wrapped, fall back to C++ names + if (key_type_name == "%") { + key_type_name = detail::type_info_description(typeid(KeyType)); + } + // Similarly for value type: + if (mapped_type_name == "%") { + mapped_type_name = detail::type_info_description(typeid(MappedType)); + } + + // Wrap KeysView[KeyType] if it wasn't already wrapped + if (!detail::get_type_info(typeid(KeysView))) { + class_ keys_view( + scope, ("KeysView[" + key_type_name + "]").c_str(), pybind11::module_local(local)); + keys_view.def("__len__", &KeysView::len); + keys_view.def("__iter__", + &KeysView::iter, + keep_alive<0, 1>() /* Essential: keep view alive while iterator exists */ + ); + keys_view.def("__contains__", + static_cast(&KeysView::contains)); + // Fallback for when the object is not of the key type + keys_view.def("__contains__", + static_cast(&KeysView::contains)); + } + // Similarly for ValuesView: + if (!detail::get_type_info(typeid(ValuesView))) { + class_ values_view(scope, + ("ValuesView[" + mapped_type_name + "]").c_str(), + pybind11::module_local(local)); + values_view.def("__len__", &ValuesView::len); + values_view.def("__iter__", + &ValuesView::iter, + keep_alive<0, 1>() /* Essential: keep view alive while iterator exists */ + ); + } + // Similarly for ItemsView: + if (!detail::get_type_info(typeid(ItemsView))) { + class_ items_view( + scope, + ("ItemsView[" + key_type_name + ", ").append(mapped_type_name + "]").c_str(), + pybind11::module_local(local)); + items_view.def("__len__", &ItemsView::len); + items_view.def("__iter__", + &ItemsView::iter, + keep_alive<0, 1>() /* Essential: keep view alive while iterator exists */ + ); + } cl.def(init<>()); @@ -698,19 +788,25 @@ class_ bind_map(handle scope, const std::string &name, Args && cl.def( "keys", - [](Map &m) { return KeysView{m}; }, + [](Map &m) { + return std::unique_ptr(new detail::KeysViewImpl(m)); + }, keep_alive<0, 1>() /* Essential: keep map alive while view exists */ ); cl.def( "values", - [](Map &m) { return ValuesView{m}; }, + [](Map &m) { + return std::unique_ptr(new detail::ValuesViewImpl(m)); + }, keep_alive<0, 1>() /* Essential: keep map alive while view exists */ ); cl.def( "items", - [](Map &m) { return ItemsView{m}; }, + [](Map &m) { + return std::unique_ptr(new detail::ItemsViewImpl(m)); + }, keep_alive<0, 1>() /* Essential: keep map alive while view exists */ ); @@ -749,36 +845,6 @@ class_ bind_map(handle scope, const std::string &name, Args && cl.def("__len__", &Map::size); - keys_view.def("__len__", [](KeysView &view) { return view.map.size(); }); - keys_view.def( - "__iter__", - [](KeysView &view) { return make_key_iterator(view.map.begin(), view.map.end()); }, - keep_alive<0, 1>() /* Essential: keep view alive while iterator exists */ - ); - keys_view.def("__contains__", [](KeysView &view, const KeyType &k) -> bool { - auto it = view.map.find(k); - if (it == view.map.end()) { - return false; - } - return true; - }); - // Fallback for when the object is not of the key type - keys_view.def("__contains__", [](KeysView &, const object &) -> bool { return false; }); - - values_view.def("__len__", [](ValuesView &view) { return view.map.size(); }); - values_view.def( - "__iter__", - [](ValuesView &view) { return make_value_iterator(view.map.begin(), view.map.end()); }, - keep_alive<0, 1>() /* Essential: keep view alive while iterator exists */ - ); - - items_view.def("__len__", [](ItemsView &view) { return view.map.size(); }); - items_view.def( - "__iter__", - [](ItemsView &view) { return make_iterator(view.map.begin(), view.map.end()); }, - keep_alive<0, 1>() /* Essential: keep view alive while iterator exists */ - ); - return cl; } diff --git a/pybind11/include/pybind11/type_caster_pyobject_ptr.h b/pybind11/include/pybind11/type_caster_pyobject_ptr.h new file mode 100644 index 000000000..aa914f9e1 --- /dev/null +++ b/pybind11/include/pybind11/type_caster_pyobject_ptr.h @@ -0,0 +1,61 @@ +// Copyright (c) 2023 The pybind Community. + +#pragma once + +#include "detail/common.h" +#include "detail/descr.h" +#include "cast.h" +#include "pytypes.h" + +PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE) +PYBIND11_NAMESPACE_BEGIN(detail) + +template <> +class type_caster { +public: + static constexpr auto name = const_name("object"); // See discussion under PR #4601. + + // This overload is purely to guard against accidents. + template ::value, int> = 0> + static handle cast(T &&, return_value_policy, handle /*parent*/) { + static_assert(is_same_ignoring_cvref::value, + "Invalid C++ type T for to-Python conversion (type_caster)."); + return nullptr; // Unreachable. + } + + static handle cast(PyObject *src, return_value_policy policy, handle /*parent*/) { + if (src == nullptr) { + throw error_already_set(); + } + if (PyErr_Occurred()) { + raise_from(PyExc_SystemError, "src != nullptr but PyErr_Occurred()"); + throw error_already_set(); + } + if (policy == return_value_policy::take_ownership) { + return src; + } + if (policy == return_value_policy::reference + || policy == return_value_policy::automatic_reference) { + return handle(src).inc_ref(); + } + pybind11_fail("type_caster::cast(): unsupported return_value_policy: " + + std::to_string(static_cast(policy))); + } + + bool load(handle src, bool) { + value = reinterpret_borrow(src); + return true; + } + + template + using cast_op_type = PyObject *; + + explicit operator PyObject *() { return value.ptr(); } + +private: + object value; +}; + +PYBIND11_NAMESPACE_END(detail) +PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE) diff --git a/pybind11/noxfile.py b/pybind11/noxfile.py index fe36d70fe..021ced245 100644 --- a/pybind11/noxfile.py +++ b/pybind11/noxfile.py @@ -5,7 +5,17 @@ import nox nox.needs_version = ">=2022.1.7" nox.options.sessions = ["lint", "tests", "tests_packaging"] -PYTHON_VERISONS = ["3.6", "3.7", "3.8", "3.9", "3.10", "3.11", "pypy3.7", "pypy3.8"] +PYTHON_VERSIONS = [ + "3.6", + "3.7", + "3.8", + "3.9", + "3.10", + "3.11", + "pypy3.7", + "pypy3.8", + "pypy3.9", +] if os.environ.get("CI", None): nox.options.error_on_missing_interpreters = True @@ -17,10 +27,10 @@ def lint(session: nox.Session) -> None: Lint the codebase (except for clang-format/tidy). """ session.install("pre-commit") - session.run("pre-commit", "run", "-a") + session.run("pre-commit", "run", "-a", *session.posargs) -@nox.session(python=PYTHON_VERISONS) +@nox.session(python=PYTHON_VERSIONS) def tests(session: nox.Session) -> None: """ Run the tests (requires a compiler). @@ -48,7 +58,7 @@ def tests_packaging(session: nox.Session) -> None: """ session.install("-r", "tests/requirements.txt", "--prefer-binary") - session.run("pytest", "tests/extra_python_package") + session.run("pytest", "tests/extra_python_package", *session.posargs) @nox.session(reuse_venv=True) diff --git a/pybind11/pybind11/__init__.py b/pybind11/pybind11/__init__.py index a1be96981..7c10b3057 100644 --- a/pybind11/pybind11/__init__.py +++ b/pybind11/pybind11/__init__.py @@ -1,16 +1,17 @@ import sys -if sys.version_info < (3, 6): +if sys.version_info < (3, 6): # noqa: UP036 msg = "pybind11 does not support Python < 3.6. 2.9 was the last release supporting Python 2.7 and 3.5." raise ImportError(msg) from ._version import __version__, version_info -from .commands import get_cmake_dir, get_include +from .commands import get_cmake_dir, get_include, get_pkgconfig_dir __all__ = ( "version_info", "__version__", "get_include", "get_cmake_dir", + "get_pkgconfig_dir", ) diff --git a/pybind11/pybind11/__main__.py b/pybind11/pybind11/__main__.py index 22fc4471e..180665c23 100644 --- a/pybind11/pybind11/__main__.py +++ b/pybind11/pybind11/__main__.py @@ -4,7 +4,8 @@ import argparse import sys import sysconfig -from .commands import get_cmake_dir, get_include +from ._version import __version__ +from .commands import get_cmake_dir, get_include, get_pkgconfig_dir def print_includes() -> None: @@ -24,8 +25,13 @@ def print_includes() -> None: def main() -> None: - parser = argparse.ArgumentParser() + parser.add_argument( + "--version", + action="version", + version=__version__, + help="Print the version and exit.", + ) parser.add_argument( "--includes", action="store_true", @@ -36,6 +42,11 @@ def main() -> None: action="store_true", help="Print the CMake module directory, ideal for setting -Dpybind11_ROOT in CMake.", ) + parser.add_argument( + "--pkgconfigdir", + action="store_true", + help="Print the pkgconfig directory, ideal for setting $PKG_CONFIG_PATH.", + ) args = parser.parse_args() if not sys.argv[1:]: parser.print_help() @@ -43,6 +54,8 @@ def main() -> None: print_includes() if args.cmakedir: print(get_cmake_dir()) + if args.pkgconfigdir: + print(get_pkgconfig_dir()) if __name__ == "__main__": diff --git a/pybind11/pybind11/_version.py b/pybind11/pybind11/_version.py index b78d5963d..9280fa054 100644 --- a/pybind11/pybind11/_version.py +++ b/pybind11/pybind11/_version.py @@ -8,5 +8,5 @@ def _to_int(s: str) -> Union[int, str]: return s -__version__ = "2.10.0" +__version__ = "2.11.1" version_info = tuple(_to_int(s) for s in __version__.split(".")) diff --git a/pybind11/pybind11/commands.py b/pybind11/pybind11/commands.py index a29c8ca61..b11690f46 100644 --- a/pybind11/pybind11/commands.py +++ b/pybind11/pybind11/commands.py @@ -3,7 +3,7 @@ import os DIR = os.path.abspath(os.path.dirname(__file__)) -def get_include(user: bool = False) -> str: # pylint: disable=unused-argument +def get_include(user: bool = False) -> str: # noqa: ARG001 """ Return the path to the pybind11 include directory. The historical "user" argument is unused, and may be removed. @@ -23,3 +23,15 @@ def get_cmake_dir() -> str: msg = "pybind11 not installed, installation required to access the CMake files" raise ImportError(msg) + + +def get_pkgconfig_dir() -> str: + """ + Return the path to the pybind11 pkgconfig directory. + """ + pkgconfig_installed_path = os.path.join(DIR, "share", "pkgconfig") + if os.path.exists(pkgconfig_installed_path): + return pkgconfig_installed_path + + msg = "pybind11 not installed, installation required to access the pkgconfig files" + raise ImportError(msg) diff --git a/pybind11/pybind11/setup_helpers.py b/pybind11/pybind11/setup_helpers.py index 1fd04b915..aeeee9dcf 100644 --- a/pybind11/pybind11/setup_helpers.py +++ b/pybind11/pybind11/setup_helpers.py @@ -66,8 +66,8 @@ try: from setuptools import Extension as _Extension from setuptools.command.build_ext import build_ext as _build_ext except ImportError: - from distutils.command.build_ext import build_ext as _build_ext - from distutils.extension import Extension as _Extension + from distutils.command.build_ext import build_ext as _build_ext # type: ignore[assignment] + from distutils.extension import Extension as _Extension # type: ignore[assignment] import distutils.ccompiler import distutils.errors @@ -84,7 +84,7 @@ STD_TMPL = "/std:c++{}" if WIN else "-std=c++{}" # directory into your path if it sits beside your setup.py. -class Pybind11Extension(_Extension): # type: ignore[misc] +class Pybind11Extension(_Extension): """ Build a C++11+ Extension module with pybind11. This automatically adds the recommended flags when you init the extension and assumes C++ sources - you @@ -118,7 +118,6 @@ class Pybind11Extension(_Extension): # type: ignore[misc] self.extra_link_args[:0] = flags def __init__(self, *args: Any, **kwargs: Any) -> None: - self._cxx_level = 0 cxx_std = kwargs.pop("cxx_std", 0) @@ -145,7 +144,6 @@ class Pybind11Extension(_Extension): # type: ignore[misc] self.cxx_std = cxx_std cflags = [] - ldflags = [] if WIN: cflags += ["/EHsc", "/bigobj"] else: @@ -155,11 +153,7 @@ class Pybind11Extension(_Extension): # type: ignore[misc] c_cpp_flags = shlex.split(env_cflags) + shlex.split(env_cppflags) if not any(opt.startswith("-g") for opt in c_cpp_flags): cflags += ["-g0"] - if MACOS: - cflags += ["-stdlib=libc++"] - ldflags += ["-stdlib=libc++"] self._add_cflags(cflags) - self._add_ldflags(ldflags) @property def cxx_std(self) -> int: @@ -174,9 +168,10 @@ class Pybind11Extension(_Extension): # type: ignore[misc] @cxx_std.setter def cxx_std(self, level: int) -> None: - if self._cxx_level: - warnings.warn("You cannot safely change the cxx_level after setting it!") + warnings.warn( + "You cannot safely change the cxx_level after setting it!", stacklevel=2 + ) # MSVC 2015 Update 3 and later only have 14 (and later 17) modes, so # force a valid flag here. @@ -271,7 +266,7 @@ def auto_cpp_level(compiler: Any) -> Union[str, int]: raise RuntimeError(msg) -class build_ext(_build_ext): # type: ignore[misc] # noqa: N801 +class build_ext(_build_ext): # noqa: N801 """ Customized build_ext that allows an auto-search for the highest supported C++ level for Pybind11Extension. This is only needed for the auto-search @@ -341,7 +336,7 @@ def naive_recompile(obj: str, src: str) -> bool: return os.stat(obj).st_mtime < os.stat(src).st_mtime -def no_recompile(obg: str, src: str) -> bool: # pylint: disable=unused-argument +def no_recompile(obg: str, src: str) -> bool: # noqa: ARG001 """ This is the safest but slowest choice (and is the default) - will always recompile sources. @@ -439,7 +434,6 @@ class ParallelCompile: extra_postargs: Optional[List[str]] = None, depends: Optional[List[str]] = None, ) -> Any: - # These lines are directly from distutils.ccompiler.CCompiler macros, objects, extra_postargs, pp_opts, build = compiler._setup_compile( # type: ignore[attr-defined] output_dir, macros, include_dirs, sources, depends, extra_postargs diff --git a/pybind11/pyproject.toml b/pybind11/pyproject.toml index 3ba1b4b22..59c15ea63 100644 --- a/pybind11/pyproject.toml +++ b/pybind11/pyproject.toml @@ -2,6 +2,7 @@ requires = ["setuptools>=42", "cmake>=3.18", "ninja"] build-backend = "setuptools.build_meta" + [tool.check-manifest] ignore = [ "tests/**", @@ -15,11 +16,6 @@ ignore = [ "noxfile.py", ] -[tool.isort] -# Needs the compiled .so modules and env.py from tests -known_first_party = "env,pybind11_cross_module_tests,pybind11_tests," -# For black compatibility -profile = "black" [tool.mypy] files = ["pybind11"] @@ -30,7 +26,7 @@ enable_error_code = ["ignore-without-code", "redundant-expr", "truthy-bool"] warn_unreachable = true [[tool.mypy.overrides]] -module = ["ghapi.*", "setuptools.*"] +module = ["ghapi.*"] ignore_missing_imports = true @@ -58,4 +54,45 @@ messages_control.disable = [ "invalid-name", "protected-access", "missing-module-docstring", + "unused-argument", # covered by Ruff ARG ] + + +[tool.ruff] +select = [ + "E", "F", "W", # flake8 + "B", # flake8-bugbear + "I", # isort + "N", # pep8-naming + "ARG", # flake8-unused-arguments + "C4", # flake8-comprehensions + "EM", # flake8-errmsg + "ICN", # flake8-import-conventions + "ISC", # flake8-implicit-str-concat + "PGH", # pygrep-hooks + "PIE", # flake8-pie + "PL", # pylint + "PT", # flake8-pytest-style + "RET", # flake8-return + "RUF100", # Ruff-specific + "SIM", # flake8-simplify + "UP", # pyupgrade + "YTT", # flake8-2020 +] +ignore = [ + "PLR", # Design related pylint + "E501", # Line too long (Black is enough) + "PT011", # Too broad with raises in pytest + "PT004", # Fixture that doesn't return needs underscore (no, it is fine) + "SIM118", # iter(x) is not always the same as iter(x.keys()) +] +target-version = "py37" +src = ["src"] +unfixable = ["T20"] +exclude = [] +line-length = 120 +isort.known-first-party = ["env", "pybind11_cross_module_tests", "pybind11_tests"] + +[tool.ruff.per-file-ignores] +"tests/**" = ["EM", "N"] +"tests/test_call_policies.py" = ["PLC1901"] diff --git a/pybind11/setup.cfg b/pybind11/setup.cfg index 36fbfed4f..92e6c953a 100644 --- a/pybind11/setup.cfg +++ b/pybind11/setup.cfg @@ -20,6 +20,7 @@ classifiers = Programming Language :: Python :: 3.9 Programming Language :: Python :: 3.10 Programming Language :: Python :: 3.11 + Programming Language :: Python :: 3.12 License :: OSI Approved :: BSD License Programming Language :: Python :: Implementation :: PyPy Programming Language :: Python :: Implementation :: CPython @@ -40,11 +41,3 @@ project_urls = [options] python_requires = >=3.6 zip_safe = False - - -[flake8] -max-line-length = 120 -show_source = True -exclude = .git, __pycache__, build, dist, docs, tools, venv -extend-ignore = E203, E722, B950 -extend-select = B9 diff --git a/pybind11/setup.py b/pybind11/setup.py index a149755bf..9fea7d35c 100644 --- a/pybind11/setup.py +++ b/pybind11/setup.py @@ -96,7 +96,7 @@ def get_and_replace( # Use our input files instead when making the SDist (and anything that depends # on it, like a wheel) -class SDist(setuptools.command.sdist.sdist): # type: ignore[misc] +class SDist(setuptools.command.sdist.sdist): def make_release_tree(self, base_dir: str, files: List[str]) -> None: super().make_release_tree(base_dir, files) @@ -127,6 +127,7 @@ with remove_output("pybind11/include", "pybind11/share"): "-DCMAKE_INSTALL_PREFIX=pybind11", "-DBUILD_TESTING=OFF", "-DPYBIND11_NOPYTHON=ON", + "-Dprefix_for_pc_file=${pcfiledir}/../../", ] if "CMAKE_ARGS" in os.environ: fcommand = [ diff --git a/pybind11/tests/CMakeLists.txt b/pybind11/tests/CMakeLists.txt index 7296cd1b8..80ee9c1f1 100644 --- a/pybind11/tests/CMakeLists.txt +++ b/pybind11/tests/CMakeLists.txt @@ -5,20 +5,17 @@ # All rights reserved. Use of this source code is governed by a # BSD-style license that can be found in the LICENSE file. -cmake_minimum_required(VERSION 3.4) +cmake_minimum_required(VERSION 3.5) -# The `cmake_minimum_required(VERSION 3.4...3.18)` syntax does not work with +# The `cmake_minimum_required(VERSION 3.5...3.26)` syntax does not work with # some versions of VS that have a patched CMake 3.11. This forces us to emulate # the behavior using the following workaround: -if(${CMAKE_VERSION} VERSION_LESS 3.21) +if(${CMAKE_VERSION} VERSION_LESS 3.26) cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}) else() - cmake_policy(VERSION 3.21) + cmake_policy(VERSION 3.26) endif() -# Only needed for CMake < 3.5 support -include(CMakeParseArguments) - # Filter out items; print an optional message if any items filtered. This ignores extensions. # # Usage: @@ -128,7 +125,8 @@ set(PYBIND11_TEST_FILES test_custom_type_casters test_custom_type_setup test_docstring_options - test_eigen + test_eigen_matrix + test_eigen_tensor test_enum test_eval test_exceptions @@ -153,7 +151,11 @@ set(PYBIND11_TEST_FILES test_stl_binders test_tagbased_polymorphic test_thread + test_type_caster_pyobject_ptr test_union + test_unnamed_namespace_a + test_unnamed_namespace_b + test_vector_unique_ptr_member test_virtual_functions) # Invoking cmake with something like: @@ -168,7 +170,7 @@ if(PYBIND11_TEST_OVERRIDE) # This allows the override to be done with extensions, preserving backwards compatibility. foreach(test_name ${TEST_FILES_NO_EXT}) if(NOT ${test_name} IN_LIST TEST_OVERRIDE_NO_EXT - )# If not in the whitelist, add to be filtered out. + )# If not in the allowlist, add to be filtered out. list(APPEND PYBIND11_TEST_FILTER ${test_name}) endif() endforeach() @@ -233,7 +235,10 @@ list(GET PYBIND11_EIGEN_VERSION_AND_HASH 1 PYBIND11_EIGEN_VERSION_HASH) # Check if Eigen is available; if not, remove from PYBIND11_TEST_FILES (but # keep it in PYBIND11_PYTEST_FILES, so that we get the "eigen is not installed" # skip message). -list(FIND PYBIND11_TEST_FILES test_eigen.cpp PYBIND11_TEST_FILES_EIGEN_I) +list(FIND PYBIND11_TEST_FILES test_eigen_matrix.cpp PYBIND11_TEST_FILES_EIGEN_I) +if(PYBIND11_TEST_FILES_EIGEN_I EQUAL -1) + list(FIND PYBIND11_TEST_FILES test_eigen_tensor.cpp PYBIND11_TEST_FILES_EIGEN_I) +endif() if(PYBIND11_TEST_FILES_EIGEN_I GREATER -1) # Try loading via newer Eigen's Eigen3Config first (bypassing tools/FindEigen3.cmake). # Eigen 3.3.1+ exports a cmake 3.0+ target for handling dependency requirements, but also @@ -288,13 +293,34 @@ if(PYBIND11_TEST_FILES_EIGEN_I GREATER -1) set(EIGEN3_VERSION ${EIGEN3_VERSION_STRING}) endif() message(STATUS "Building tests with Eigen v${EIGEN3_VERSION}") + + if(NOT (CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 5.0)) + tests_extra_targets("test_eigen_tensor.py" "eigen_tensor_avoid_stl_array") + endif() + else() - list(REMOVE_AT PYBIND11_TEST_FILES ${PYBIND11_TEST_FILES_EIGEN_I}) + list(FIND PYBIND11_TEST_FILES test_eigen_matrix.cpp PYBIND11_TEST_FILES_EIGEN_I) + if(PYBIND11_TEST_FILES_EIGEN_I GREATER -1) + list(REMOVE_AT PYBIND11_TEST_FILES ${PYBIND11_TEST_FILES_EIGEN_I}) + endif() + + list(FIND PYBIND11_TEST_FILES test_eigen_tensor.cpp PYBIND11_TEST_FILES_EIGEN_I) + if(PYBIND11_TEST_FILES_EIGEN_I GREATER -1) + list(REMOVE_AT PYBIND11_TEST_FILES ${PYBIND11_TEST_FILES_EIGEN_I}) + endif() message( STATUS "Building tests WITHOUT Eigen, use -DDOWNLOAD_EIGEN=ON on CMake 3.11+ to download") endif() endif() +# Some code doesn't support gcc 4 +if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 5.0) + list(FIND PYBIND11_TEST_FILES test_eigen_tensor.cpp PYBIND11_TEST_FILES_EIGEN_I) + if(PYBIND11_TEST_FILES_EIGEN_I GREATER -1) + list(REMOVE_AT PYBIND11_TEST_FILES ${PYBIND11_TEST_FILES_EIGEN_I}) + endif() +endif() + # Optional dependency for some tests (boost::variant is only supported with version >= 1.56) find_package(Boost 1.56) diff --git a/pybind11/tests/conftest.py b/pybind11/tests/conftest.py index 02ce263af..ad5b47b4b 100644 --- a/pybind11/tests/conftest.py +++ b/pybind11/tests/conftest.py @@ -7,13 +7,36 @@ Adds docstring and exceptions message sanitizers. import contextlib import difflib import gc +import multiprocessing import re +import sys import textwrap +import traceback import pytest # Early diagnostic for failed imports -import pybind11_tests +try: + import pybind11_tests +except Exception: + # pytest does not show the traceback without this. + traceback.print_exc() + raise + + +@pytest.fixture(scope="session", autouse=True) +def use_multiprocessing_forkserver_on_linux(): + if sys.platform != "linux": + # The default on Windows and macOS is "spawn": If it's not broken, don't fix it. + return + + # Full background: https://github.com/pybind/pybind11/issues/4105#issuecomment-1301004592 + # In a nutshell: fork() after starting threads == flakiness in the form of deadlocks. + # It is actually a well-known pitfall, unfortunately without guard rails. + # "forkserver" is more performant than "spawn" (~9s vs ~13s for tests/test_gil_scoped.py, + # visit the issuecomment link above for details). + multiprocessing.set_start_method("forkserver") + _long_marker = re.compile(r"([0-9])L") _hexadecimal = re.compile(r"0x[0-9a-fA-F]+") @@ -59,9 +82,8 @@ class Output: b = _strip_and_dedent(other).splitlines() if a == b: return True - else: - self.explanation = _make_explanation(a, b) - return False + self.explanation = _make_explanation(a, b) + return False class Unordered(Output): @@ -72,9 +94,8 @@ class Unordered(Output): b = _split_and_sort(other) if a == b: return True - else: - self.explanation = _make_explanation(a, b) - return False + self.explanation = _make_explanation(a, b) + return False class Capture: @@ -95,9 +116,8 @@ class Capture: b = other if a == b: return True - else: - self.explanation = a.explanation - return False + self.explanation = a.explanation + return False def __str__(self): return self.out @@ -114,7 +134,7 @@ class Capture: return Output(self.err) -@pytest.fixture +@pytest.fixture() def capture(capsys): """Extended `capsys` with context manager and custom equality operators""" return Capture(capsys) @@ -135,25 +155,22 @@ class SanitizedString: b = _strip_and_dedent(other) if a == b: return True - else: - self.explanation = _make_explanation(a.splitlines(), b.splitlines()) - return False + self.explanation = _make_explanation(a.splitlines(), b.splitlines()) + return False def _sanitize_general(s): s = s.strip() s = s.replace("pybind11_tests.", "m.") - s = _long_marker.sub(r"\1", s) - return s + return _long_marker.sub(r"\1", s) def _sanitize_docstring(thing): s = thing.__doc__ - s = _sanitize_general(s) - return s + return _sanitize_general(s) -@pytest.fixture +@pytest.fixture() def doc(): """Sanitize docstrings and add custom failure explanation""" return SanitizedString(_sanitize_docstring) @@ -162,30 +179,20 @@ def doc(): def _sanitize_message(thing): s = str(thing) s = _sanitize_general(s) - s = _hexadecimal.sub("0", s) - return s + return _hexadecimal.sub("0", s) -@pytest.fixture +@pytest.fixture() def msg(): """Sanitize messages and add custom failure explanation""" return SanitizedString(_sanitize_message) -# noinspection PyUnusedLocal -def pytest_assertrepr_compare(op, left, right): +def pytest_assertrepr_compare(op, left, right): # noqa: ARG001 """Hook to insert custom failure explanation""" if hasattr(left, "explanation"): return left.explanation - - -@contextlib.contextmanager -def suppress(exception): - """Suppress the desired exception""" - try: - yield - except exception: - pass + return None def gc_collect(): @@ -196,7 +203,7 @@ def gc_collect(): def pytest_configure(): - pytest.suppress = suppress + pytest.suppress = contextlib.suppress pytest.gc_collect = gc_collect @@ -210,4 +217,5 @@ def pytest_report_header(config): f" {pybind11_tests.compiler_info}" f" {pybind11_tests.cpp_std}" f" {pybind11_tests.PYBIND11_INTERNALS_ID}" + f" PYBIND11_SIMPLE_GIL_MANAGEMENT={pybind11_tests.PYBIND11_SIMPLE_GIL_MANAGEMENT}" ) diff --git a/pybind11/tests/constructor_stats.h b/pybind11/tests/constructor_stats.h index a3835c21e..937f6c233 100644 --- a/pybind11/tests/constructor_stats.h +++ b/pybind11/tests/constructor_stats.h @@ -115,7 +115,7 @@ public: #if defined(PYPY_VERSION) PyObject *globals = PyEval_GetGlobals(); PyObject *result = PyRun_String("import gc\n" - "for i in range(2):" + "for i in range(2):\n" " gc.collect()\n", Py_file_input, globals, diff --git a/pybind11/tests/cross_module_gil_utils.cpp b/pybind11/tests/cross_module_gil_utils.cpp index 1436c35d6..7c20849dd 100644 --- a/pybind11/tests/cross_module_gil_utils.cpp +++ b/pybind11/tests/cross_module_gil_utils.cpp @@ -6,9 +6,15 @@ All rights reserved. Use of this source code is governed by a BSD-style license that can be found in the LICENSE file. */ +#if defined(PYBIND11_INTERNALS_VERSION) +# undef PYBIND11_INTERNALS_VERSION +#endif +#define PYBIND11_INTERNALS_VERSION 21814642 // Ensure this module has its own `internals` instance. #include #include +#include +#include // This file mimics a DSO that makes pybind11 calls but does not define a // PYBIND11_MODULE. The purpose is to test that such a DSO can create a @@ -21,8 +27,54 @@ namespace { namespace py = pybind11; + void gil_acquire() { py::gil_scoped_acquire gil; } +std::string gil_multi_acquire_release(unsigned bits) { + if ((bits & 0x1u) != 0u) { + py::gil_scoped_acquire gil; + } + if ((bits & 0x2u) != 0u) { + py::gil_scoped_release gil; + } + if ((bits & 0x4u) != 0u) { + py::gil_scoped_acquire gil; + } + if ((bits & 0x8u) != 0u) { + py::gil_scoped_release gil; + } + return PYBIND11_INTERNALS_ID; +} + +struct CustomAutoGIL { + CustomAutoGIL() : gstate(PyGILState_Ensure()) {} + ~CustomAutoGIL() { PyGILState_Release(gstate); } + + PyGILState_STATE gstate; +}; +struct CustomAutoNoGIL { + CustomAutoNoGIL() : save(PyEval_SaveThread()) {} + ~CustomAutoNoGIL() { PyEval_RestoreThread(save); } + + PyThreadState *save; +}; + +template +void gil_acquire_inner() { + Acquire acquire_outer; + Acquire acquire_inner; + Release release; +} + +template +void gil_acquire_nested() { + Acquire acquire_outer; + Acquire acquire_inner; + Release release; + auto thread = std::thread(&gil_acquire_inner); + thread.join(); +} + constexpr char kModuleName[] = "cross_module_gil_utils"; struct PyModuleDef moduledef = { @@ -30,6 +82,9 @@ struct PyModuleDef moduledef = { } // namespace +#define ADD_FUNCTION(Name, ...) \ + PyModule_AddObject(m, Name, PyLong_FromVoidPtr(reinterpret_cast(&__VA_ARGS__))); + extern "C" PYBIND11_EXPORT PyObject *PyInit_cross_module_gil_utils() { PyObject *m = PyModule_Create(&moduledef); @@ -37,8 +92,16 @@ extern "C" PYBIND11_EXPORT PyObject *PyInit_cross_module_gil_utils() { if (m != nullptr) { static_assert(sizeof(&gil_acquire) == sizeof(void *), "Function pointer must have the same size as void*"); - PyModule_AddObject( - m, "gil_acquire_funcaddr", PyLong_FromVoidPtr(reinterpret_cast(&gil_acquire))); + ADD_FUNCTION("gil_acquire_funcaddr", gil_acquire) + ADD_FUNCTION("gil_multi_acquire_release_funcaddr", gil_multi_acquire_release) + ADD_FUNCTION("gil_acquire_inner_custom_funcaddr", + gil_acquire_inner) + ADD_FUNCTION("gil_acquire_nested_custom_funcaddr", + gil_acquire_nested) + ADD_FUNCTION("gil_acquire_inner_pybind11_funcaddr", + gil_acquire_inner) + ADD_FUNCTION("gil_acquire_nested_pybind11_funcaddr", + gil_acquire_nested) } return m; diff --git a/pybind11/tests/eigen_tensor_avoid_stl_array.cpp b/pybind11/tests/eigen_tensor_avoid_stl_array.cpp new file mode 100644 index 000000000..eacc9e9bd --- /dev/null +++ b/pybind11/tests/eigen_tensor_avoid_stl_array.cpp @@ -0,0 +1,14 @@ +/* + tests/eigen_tensor.cpp -- automatic conversion of Eigen Tensor + + All rights reserved. Use of this source code is governed by a + BSD-style license that can be found in the LICENSE file. +*/ + +#ifndef EIGEN_AVOID_STL_ARRAY +# define EIGEN_AVOID_STL_ARRAY +#endif + +#include "test_eigen_tensor.inl" + +PYBIND11_MODULE(eigen_tensor_avoid_stl_array, m) { eigen_tensor_test::test_module(m); } diff --git a/pybind11/tests/env.py b/pybind11/tests/env.py index 0345df65d..7eea5a3b3 100644 --- a/pybind11/tests/env.py +++ b/pybind11/tests/env.py @@ -24,5 +24,4 @@ def deprecated_call(): pytest_major_minor = (int(pieces[0]), int(pieces[1])) if pytest_major_minor < (3, 9): return pytest.warns((DeprecationWarning, PendingDeprecationWarning)) - else: - return pytest.deprecated_call() + return pytest.deprecated_call() diff --git a/pybind11/tests/extra_python_package/test_files.py b/pybind11/tests/extra_python_package/test_files.py index ba16b5224..57387dd8b 100644 --- a/pybind11/tests/extra_python_package/test_files.py +++ b/pybind11/tests/extra_python_package/test_files.py @@ -12,6 +12,16 @@ import zipfile DIR = os.path.abspath(os.path.dirname(__file__)) MAIN_DIR = os.path.dirname(os.path.dirname(DIR)) +PKGCONFIG = """\ +prefix=${{pcfiledir}}/../../ +includedir=${{prefix}}/include + +Name: pybind11 +Description: Seamless operability between C++11 and Python +Version: {VERSION} +Cflags: -I${{includedir}} +""" + main_headers = { "include/pybind11/attr.h", @@ -33,6 +43,7 @@ main_headers = { "include/pybind11/pytypes.h", "include/pybind11/stl.h", "include/pybind11/stl_bind.h", + "include/pybind11/type_caster_pyobject_ptr.h", } detail_headers = { @@ -45,6 +56,12 @@ detail_headers = { "include/pybind11/detail/typeid.h", } +eigen_headers = { + "include/pybind11/eigen/common.h", + "include/pybind11/eigen/matrix.h", + "include/pybind11/eigen/tensor.h", +} + stl_headers = { "include/pybind11/stl/filesystem.h", } @@ -59,6 +76,10 @@ cmake_files = { "share/cmake/pybind11/pybind11Tools.cmake", } +pkgconfig_files = { + "share/pkgconfig/pybind11.pc", +} + py_files = { "__init__.py", "__main__.py", @@ -68,8 +89,8 @@ py_files = { "setup_helpers.py", } -headers = main_headers | detail_headers | stl_headers -src_files = headers | cmake_files +headers = main_headers | detail_headers | eigen_headers | stl_headers +src_files = headers | cmake_files | pkgconfig_files all_files = src_files | py_files @@ -78,10 +99,12 @@ sdist_files = { "pybind11/include", "pybind11/include/pybind11", "pybind11/include/pybind11/detail", + "pybind11/include/pybind11/eigen", "pybind11/include/pybind11/stl", "pybind11/share", "pybind11/share/cmake", "pybind11/share/cmake/pybind11", + "pybind11/share/pkgconfig", "pyproject.toml", "setup.cfg", "setup.py", @@ -89,6 +112,7 @@ sdist_files = { "MANIFEST.in", "README.rst", "PKG-INFO", + "SECURITY.md", } local_sdist_files = { @@ -101,22 +125,24 @@ local_sdist_files = { } -def test_build_sdist(monkeypatch, tmpdir): +def read_tz_file(tar: tarfile.TarFile, name: str) -> bytes: + start = tar.getnames()[0] + "/" + inner_file = tar.extractfile(tar.getmember(f"{start}{name}")) + assert inner_file + with contextlib.closing(inner_file) as f: + return f.read() + +def normalize_line_endings(value: bytes) -> bytes: + return value.replace(os.linesep.encode("utf-8"), b"\n") + + +def test_build_sdist(monkeypatch, tmpdir): monkeypatch.chdir(MAIN_DIR) - out = subprocess.check_output( - [ - sys.executable, - "-m", - "build", - "--sdist", - "--outdir", - str(tmpdir), - ] + subprocess.run( + [sys.executable, "-m", "build", "--sdist", f"--outdir={tmpdir}"], check=True ) - if hasattr(out, "decode"): - out = out.decode() (sdist,) = tmpdir.visit("*.tar.gz") @@ -125,25 +151,17 @@ def test_build_sdist(monkeypatch, tmpdir): version = start[9:-1] simpler = {n.split("/", 1)[-1] for n in tar.getnames()[1:]} - with contextlib.closing( - tar.extractfile(tar.getmember(start + "setup.py")) - ) as f: - setup_py = f.read() + setup_py = read_tz_file(tar, "setup.py") + pyproject_toml = read_tz_file(tar, "pyproject.toml") + pkgconfig = read_tz_file(tar, "pybind11/share/pkgconfig/pybind11.pc") + cmake_cfg = read_tz_file( + tar, "pybind11/share/cmake/pybind11/pybind11Config.cmake" + ) - with contextlib.closing( - tar.extractfile(tar.getmember(start + "pyproject.toml")) - ) as f: - pyproject_toml = f.read() - - with contextlib.closing( - tar.extractfile( - tar.getmember( - start + "pybind11/share/cmake/pybind11/pybind11Config.cmake" - ) - ) - ) as f: - contents = f.read().decode("utf8") - assert 'set(pybind11_INCLUDE_DIR "${PACKAGE_PREFIX_DIR}/include")' in contents + assert ( + 'set(pybind11_INCLUDE_DIR "${PACKAGE_PREFIX_DIR}/include")' + in cmake_cfg.decode("utf-8") + ) files = {f"pybind11/{n}" for n in all_files} files |= sdist_files @@ -154,9 +172,9 @@ def test_build_sdist(monkeypatch, tmpdir): with open(os.path.join(MAIN_DIR, "tools", "setup_main.py.in"), "rb") as f: contents = ( - string.Template(f.read().decode()) + string.Template(f.read().decode("utf-8")) .substitute(version=version, extra_cmd="") - .encode() + .encode("utf-8") ) assert setup_py == contents @@ -164,25 +182,18 @@ def test_build_sdist(monkeypatch, tmpdir): contents = f.read() assert pyproject_toml == contents + simple_version = ".".join(version.split(".")[:3]) + pkgconfig_expected = PKGCONFIG.format(VERSION=simple_version).encode("utf-8") + assert normalize_line_endings(pkgconfig) == pkgconfig_expected + def test_build_global_dist(monkeypatch, tmpdir): - monkeypatch.chdir(MAIN_DIR) monkeypatch.setenv("PYBIND11_GLOBAL_SDIST", "1") - out = subprocess.check_output( - [ - sys.executable, - "-m", - "build", - "--sdist", - "--outdir", - str(tmpdir), - ] + subprocess.run( + [sys.executable, "-m", "build", "--sdist", "--outdir", str(tmpdir)], check=True ) - if hasattr(out, "decode"): - out = out.decode() - (sdist,) = tmpdir.visit("*.tar.gz") with tarfile.open(str(sdist), "r:gz") as tar: @@ -190,15 +201,17 @@ def test_build_global_dist(monkeypatch, tmpdir): version = start[16:-1] simpler = {n.split("/", 1)[-1] for n in tar.getnames()[1:]} - with contextlib.closing( - tar.extractfile(tar.getmember(start + "setup.py")) - ) as f: - setup_py = f.read() + setup_py = read_tz_file(tar, "setup.py") + pyproject_toml = read_tz_file(tar, "pyproject.toml") + pkgconfig = read_tz_file(tar, "pybind11/share/pkgconfig/pybind11.pc") + cmake_cfg = read_tz_file( + tar, "pybind11/share/cmake/pybind11/pybind11Config.cmake" + ) - with contextlib.closing( - tar.extractfile(tar.getmember(start + "pyproject.toml")) - ) as f: - pyproject_toml = f.read() + assert ( + 'set(pybind11_INCLUDE_DIR "${PACKAGE_PREFIX_DIR}/include")' + in cmake_cfg.decode("utf-8") + ) files = {f"pybind11/{n}" for n in all_files} files |= sdist_files @@ -209,7 +222,7 @@ def test_build_global_dist(monkeypatch, tmpdir): contents = ( string.Template(f.read().decode()) .substitute(version=version, extra_cmd="") - .encode() + .encode("utf-8") ) assert setup_py == contents @@ -217,12 +230,16 @@ def test_build_global_dist(monkeypatch, tmpdir): contents = f.read() assert pyproject_toml == contents + simple_version = ".".join(version.split(".")[:3]) + pkgconfig_expected = PKGCONFIG.format(VERSION=simple_version).encode("utf-8") + assert normalize_line_endings(pkgconfig) == pkgconfig_expected + def tests_build_wheel(monkeypatch, tmpdir): monkeypatch.chdir(MAIN_DIR) - subprocess.check_output( - [sys.executable, "-m", "pip", "wheel", ".", "-w", str(tmpdir)] + subprocess.run( + [sys.executable, "-m", "pip", "wheel", ".", "-w", str(tmpdir)], check=True ) (wheel,) = tmpdir.visit("*.whl") @@ -249,8 +266,8 @@ def tests_build_global_wheel(monkeypatch, tmpdir): monkeypatch.chdir(MAIN_DIR) monkeypatch.setenv("PYBIND11_GLOBAL_SDIST", "1") - subprocess.check_output( - [sys.executable, "-m", "pip", "wheel", ".", "-w", str(tmpdir)] + subprocess.run( + [sys.executable, "-m", "pip", "wheel", ".", "-w", str(tmpdir)], check=True ) (wheel,) = tmpdir.visit("*.whl") diff --git a/pybind11/tests/pybind11_tests.cpp b/pybind11/tests/pybind11_tests.cpp index aa3095594..624034648 100644 --- a/pybind11/tests/pybind11_tests.cpp +++ b/pybind11/tests/pybind11_tests.cpp @@ -89,6 +89,12 @@ PYBIND11_MODULE(pybind11_tests, m) { #endif m.attr("cpp_std") = cpp_std(); m.attr("PYBIND11_INTERNALS_ID") = PYBIND11_INTERNALS_ID; + m.attr("PYBIND11_SIMPLE_GIL_MANAGEMENT") = +#if defined(PYBIND11_SIMPLE_GIL_MANAGEMENT) + true; +#else + false; +#endif bind_ConstructorStats(m); diff --git a/pybind11/tests/requirements.txt b/pybind11/tests/requirements.txt index 04aafa8cf..4ba101119 100644 --- a/pybind11/tests/requirements.txt +++ b/pybind11/tests/requirements.txt @@ -6,4 +6,4 @@ numpy==1.22.2; platform_python_implementation!="PyPy" and python_version>="3.10" pytest==7.0.0 pytest-timeout scipy==1.5.4; platform_python_implementation!="PyPy" and python_version<"3.10" -scipy==1.8.0; platform_python_implementation!="PyPy" and python_version=="3.10" +scipy==1.10.0; platform_python_implementation!="PyPy" and python_version=="3.10" diff --git a/pybind11/tests/test_async.py b/pybind11/tests/test_async.py index b9ff9514d..83a4c5036 100644 --- a/pybind11/tests/test_async.py +++ b/pybind11/tests/test_async.py @@ -4,7 +4,7 @@ asyncio = pytest.importorskip("asyncio") m = pytest.importorskip("pybind11_tests.async_module") -@pytest.fixture +@pytest.fixture() def event_loop(): loop = asyncio.new_event_loop() yield loop @@ -16,7 +16,7 @@ async def get_await_result(x): def test_await(event_loop): - assert 5 == event_loop.run_until_complete(get_await_result(m.SupportsAsync())) + assert event_loop.run_until_complete(get_await_result(m.SupportsAsync())) == 5 def test_await_missing(event_loop): diff --git a/pybind11/tests/test_buffers.cpp b/pybind11/tests/test_buffers.cpp index 6b6e8cba7..b5b8c355b 100644 --- a/pybind11/tests/test_buffers.cpp +++ b/pybind11/tests/test_buffers.cpp @@ -7,12 +7,47 @@ BSD-style license that can be found in the LICENSE file. */ +#include #include #include "constructor_stats.h" #include "pybind11_tests.h" TEST_SUBMODULE(buffers, m) { + m.attr("long_double_and_double_have_same_size") = (sizeof(long double) == sizeof(double)); + + m.def("format_descriptor_format_buffer_info_equiv", + [](const std::string &cpp_name, const py::buffer &buffer) { + // https://google.github.io/styleguide/cppguide.html#Static_and_Global_Variables + static auto *format_table = new std::map; + static auto *equiv_table + = new std::map; + if (format_table->empty()) { +#define PYBIND11_ASSIGN_HELPER(...) \ + (*format_table)[#__VA_ARGS__] = py::format_descriptor<__VA_ARGS__>::format(); \ + (*equiv_table)[#__VA_ARGS__] = &py::buffer_info::item_type_is_equivalent_to<__VA_ARGS__>; + PYBIND11_ASSIGN_HELPER(PyObject *) + PYBIND11_ASSIGN_HELPER(bool) + PYBIND11_ASSIGN_HELPER(std::int8_t) + PYBIND11_ASSIGN_HELPER(std::uint8_t) + PYBIND11_ASSIGN_HELPER(std::int16_t) + PYBIND11_ASSIGN_HELPER(std::uint16_t) + PYBIND11_ASSIGN_HELPER(std::int32_t) + PYBIND11_ASSIGN_HELPER(std::uint32_t) + PYBIND11_ASSIGN_HELPER(std::int64_t) + PYBIND11_ASSIGN_HELPER(std::uint64_t) + PYBIND11_ASSIGN_HELPER(float) + PYBIND11_ASSIGN_HELPER(double) + PYBIND11_ASSIGN_HELPER(long double) + PYBIND11_ASSIGN_HELPER(std::complex) + PYBIND11_ASSIGN_HELPER(std::complex) + PYBIND11_ASSIGN_HELPER(std::complex) +#undef PYBIND11_ASSIGN_HELPER + } + return std::pair( + (*format_table)[cpp_name], (buffer.request().*((*equiv_table)[cpp_name]))()); + }); + // test_from_python / test_to_python: class Matrix { public: diff --git a/pybind11/tests/test_buffers.py b/pybind11/tests/test_buffers.py index 8354b68cd..63d9d869f 100644 --- a/pybind11/tests/test_buffers.py +++ b/pybind11/tests/test_buffers.py @@ -10,6 +10,63 @@ from pybind11_tests import buffers as m np = pytest.importorskip("numpy") +if m.long_double_and_double_have_same_size: + # Determined by the compiler used to build the pybind11 tests + # (e.g. MSVC gets here, but MinGW might not). + np_float128 = None + np_complex256 = None +else: + # Determined by the compiler used to build numpy (e.g. MinGW). + np_float128 = getattr(np, *["float128"] * 2) + np_complex256 = getattr(np, *["complex256"] * 2) + +CPP_NAME_FORMAT_NP_DTYPE_TABLE = [ + ("PyObject *", "O", object), + ("bool", "?", np.bool_), + ("std::int8_t", "b", np.int8), + ("std::uint8_t", "B", np.uint8), + ("std::int16_t", "h", np.int16), + ("std::uint16_t", "H", np.uint16), + ("std::int32_t", "i", np.int32), + ("std::uint32_t", "I", np.uint32), + ("std::int64_t", "q", np.int64), + ("std::uint64_t", "Q", np.uint64), + ("float", "f", np.float32), + ("double", "d", np.float64), + ("long double", "g", np_float128), + ("std::complex", "Zf", np.complex64), + ("std::complex", "Zd", np.complex128), + ("std::complex", "Zg", np_complex256), +] +CPP_NAME_FORMAT_TABLE = [ + (cpp_name, format) + for cpp_name, format, np_dtype in CPP_NAME_FORMAT_NP_DTYPE_TABLE + if np_dtype is not None +] +CPP_NAME_NP_DTYPE_TABLE = [ + (cpp_name, np_dtype) for cpp_name, _, np_dtype in CPP_NAME_FORMAT_NP_DTYPE_TABLE +] + + +@pytest.mark.parametrize(("cpp_name", "np_dtype"), CPP_NAME_NP_DTYPE_TABLE) +def test_format_descriptor_format_buffer_info_equiv(cpp_name, np_dtype): + if np_dtype is None: + pytest.skip( + f"cpp_name=`{cpp_name}`: `long double` and `double` have same size." + ) + if isinstance(np_dtype, str): + pytest.skip(f"np.{np_dtype} does not exist.") + np_array = np.array([], dtype=np_dtype) + for other_cpp_name, expected_format in CPP_NAME_FORMAT_TABLE: + format, np_array_is_matching = m.format_descriptor_format_buffer_info_equiv( + other_cpp_name, np_array + ) + assert format == expected_format + if other_cpp_name == cpp_name: + assert np_array_is_matching + else: + assert not np_array_is_matching + def test_from_python(): with pytest.raises(RuntimeError) as excinfo: @@ -54,7 +111,8 @@ def test_to_python(): mat2 = np.array(mat, copy=False) assert mat2.shape == (5, 4) assert abs(mat2).sum() == 11 - assert mat2[2, 3] == 4 and mat2[3, 2] == 7 + assert mat2[2, 3] == 4 + assert mat2[3, 2] == 7 mat2[2, 3] = 5 assert mat2[2, 3] == 5 diff --git a/pybind11/tests/test_builtin_casters.cpp b/pybind11/tests/test_builtin_casters.cpp index 6529f47d0..0623b85dc 100644 --- a/pybind11/tests/test_builtin_casters.cpp +++ b/pybind11/tests/test_builtin_casters.cpp @@ -73,6 +73,9 @@ PYBIND11_NAMESPACE_END(detail) PYBIND11_NAMESPACE_END(pybind11) TEST_SUBMODULE(builtin_casters, m) { + PYBIND11_WARNING_PUSH + PYBIND11_WARNING_DISABLE_MSVC(4127) + // test_simple_string m.def("string_roundtrip", [](const char *s) { return s; }); @@ -86,7 +89,7 @@ TEST_SUBMODULE(builtin_casters, m) { std::wstring wstr; wstr.push_back(0x61); // a wstr.push_back(0x2e18); // ⸘ - if (PYBIND11_SILENCE_MSVC_C4127(sizeof(wchar_t) == 2)) { + if (sizeof(wchar_t) == 2) { wstr.push_back(mathbfA16_1); wstr.push_back(mathbfA16_2); } // 𝐀, utf16 @@ -113,7 +116,7 @@ TEST_SUBMODULE(builtin_casters, m) { // Under Python 2.7, invalid unicode UTF-32 characters didn't appear to trigger // UnicodeDecodeError m.def("bad_utf32_string", [=]() { return std::u32string({a32, char32_t(0xd800), z32}); }); - if (PYBIND11_SILENCE_MSVC_C4127(sizeof(wchar_t) == 2)) { + if (sizeof(wchar_t) == 2) { m.def("bad_wchar_string", [=]() { return std::wstring({wchar_t(0x61), wchar_t(0xd800)}); }); @@ -266,9 +269,14 @@ TEST_SUBMODULE(builtin_casters, m) { }); m.def("lvalue_nested", []() -> const decltype(lvnested) & { return lvnested; }); - static std::pair int_string_pair{2, "items"}; m.def( - "int_string_pair", []() { return &int_string_pair; }, py::return_value_policy::reference); + "int_string_pair", + []() { + // Using no-destructor idiom to side-step warnings from overzealous compilers. + static auto *int_string_pair = new std::pair{2, "items"}; + return int_string_pair; + }, + py::return_value_policy::reference); // test_builtins_cast_return_none m.def("return_none_string", []() -> std::string * { return nullptr; }); @@ -379,4 +387,6 @@ TEST_SUBMODULE(builtin_casters, m) { m.def("takes_const_ref", [](const ConstRefCasted &x) { return x.tag; }); m.def("takes_const_ref_wrap", [](std::reference_wrapper x) { return x.get().tag; }); + + PYBIND11_WARNING_POP } diff --git a/pybind11/tests/test_builtin_casters.py b/pybind11/tests/test_builtin_casters.py index d38ae6802..b1f57bdd9 100644 --- a/pybind11/tests/test_builtin_casters.py +++ b/pybind11/tests/test_builtin_casters.py @@ -126,8 +126,8 @@ def test_bytes_to_string(): assert m.strlen(b"hi") == 2 assert m.string_length(b"world") == 5 - assert m.string_length("a\x00b".encode()) == 3 - assert m.strlen("a\x00b".encode()) == 1 # C-string limitation + assert m.string_length(b"a\x00b") == 3 + assert m.strlen(b"a\x00b") == 1 # C-string limitation # passing in a utf8 encoded string should work assert m.string_length("💩".encode()) == 4 @@ -421,13 +421,15 @@ def test_reference_wrapper(): a2 = m.refwrap_list(copy=True) assert [x.value for x in a1] == [2, 3] assert [x.value for x in a2] == [2, 3] - assert not a1[0] is a2[0] and not a1[1] is a2[1] + assert a1[0] is not a2[0] + assert a1[1] is not a2[1] b1 = m.refwrap_list(copy=False) b2 = m.refwrap_list(copy=False) assert [x.value for x in b1] == [1, 2] assert [x.value for x in b2] == [1, 2] - assert b1[0] is b2[0] and b1[1] is b2[1] + assert b1[0] is b2[0] + assert b1[1] is b2[1] assert m.refwrap_iiw(IncType(5)) == 5 assert m.refwrap_call_iiw(IncType(10), m.refwrap_iiw) == [10, 10, 10, 10] diff --git a/pybind11/tests/test_callbacks.cpp b/pybind11/tests/test_callbacks.cpp index 92b8053de..2fd05dec7 100644 --- a/pybind11/tests/test_callbacks.cpp +++ b/pybind11/tests/test_callbacks.cpp @@ -240,4 +240,41 @@ TEST_SUBMODULE(callbacks, m) { f(); } }); + + auto *custom_def = []() { + static PyMethodDef def; + def.ml_name = "example_name"; + def.ml_doc = "Example doc"; + def.ml_meth = [](PyObject *, PyObject *args) -> PyObject * { + if (PyTuple_Size(args) != 1) { + throw std::runtime_error("Invalid number of arguments for example_name"); + } + PyObject *first = PyTuple_GetItem(args, 0); + if (!PyLong_Check(first)) { + throw std::runtime_error("Invalid argument to example_name"); + } + auto result = py::cast(PyLong_AsLong(first) * 9); + return result.release().ptr(); + }; + def.ml_flags = METH_VARARGS; + return &def; + }(); + + // rec_capsule with name that has the same value (but not pointer) as our internal one + // This capsule should be detected by our code as foreign and not inspected as the pointers + // shouldn't match + constexpr const char *rec_capsule_name + = pybind11::detail::internals_function_record_capsule_name; + py::capsule rec_capsule(std::malloc(1), [](void *data) { std::free(data); }); + rec_capsule.set_name(rec_capsule_name); + m.add_object("custom_function", PyCFunction_New(custom_def, rec_capsule.ptr())); + + // This test requires a new ABI version to pass +#if PYBIND11_INTERNALS_VERSION > 4 + // rec_capsule with nullptr name + py::capsule rec_capsule2(std::malloc(1), [](void *data) { std::free(data); }); + m.add_object("custom_function2", PyCFunction_New(custom_def, rec_capsule2.ptr())); +#else + m.add_object("custom_function2", py::none()); +#endif } diff --git a/pybind11/tests/test_callbacks.py b/pybind11/tests/test_callbacks.py index 0b1047bbf..4a652f53e 100644 --- a/pybind11/tests/test_callbacks.py +++ b/pybind11/tests/test_callbacks.py @@ -5,6 +5,7 @@ import pytest import env # noqa: F401 from pybind11_tests import callbacks as m +from pybind11_tests import detailed_error_messages_enabled def test_callbacks(): @@ -70,11 +71,20 @@ def test_keyword_args_and_generalized_unpacking(): with pytest.raises(RuntimeError) as excinfo: m.test_arg_conversion_error1(f) - assert "Unable to convert call argument" in str(excinfo.value) + assert str(excinfo.value) == "Unable to convert call argument " + ( + "'1' of type 'UnregisteredType' to Python object" + if detailed_error_messages_enabled + else "'1' to Python object (#define PYBIND11_DETAILED_ERROR_MESSAGES or compile in debug mode for details)" + ) with pytest.raises(RuntimeError) as excinfo: m.test_arg_conversion_error2(f) - assert "Unable to convert call argument" in str(excinfo.value) + assert str(excinfo.value) == "Unable to convert call argument " + ( + "'expected_name' of type 'UnregisteredType' to Python object" + if detailed_error_messages_enabled + else "'expected_name' to Python object " + "(#define PYBIND11_DETAILED_ERROR_MESSAGES or compile in debug mode for details)" + ) def test_lambda_closure_cleanup(): @@ -193,3 +203,16 @@ def test_callback_num_times(): if len(rates) > 1: print("Min Mean Max") print(f"{min(rates):6.3f} {sum(rates) / len(rates):6.3f} {max(rates):6.3f}") + + +def test_custom_func(): + assert m.custom_function(4) == 36 + assert m.roundtrip(m.custom_function)(4) == 36 + + +@pytest.mark.skipif( + m.custom_function2 is None, reason="Current PYBIND11_INTERNALS_VERSION too low" +) +def test_custom_func2(): + assert m.custom_function2(3) == 27 + assert m.roundtrip(m.custom_function2)(3) == 27 diff --git a/pybind11/tests/test_chrono.py b/pybind11/tests/test_chrono.py index 7f47b37a2..a29316c38 100644 --- a/pybind11/tests/test_chrono.py +++ b/pybind11/tests/test_chrono.py @@ -7,7 +7,6 @@ from pybind11_tests import chrono as m def test_chrono_system_clock(): - # Get the time from both c++ and datetime date0 = datetime.datetime.today() date1 = m.test_chrono1() @@ -122,7 +121,6 @@ def test_chrono_system_clock_roundtrip_time(time1, tz, monkeypatch): def test_chrono_duration_roundtrip(): - # Get the difference between two times (a timedelta) date1 = datetime.datetime.today() date2 = datetime.datetime.today() @@ -143,7 +141,6 @@ def test_chrono_duration_roundtrip(): def test_chrono_duration_subtraction_equivalence(): - date1 = datetime.datetime.today() date2 = datetime.datetime.today() @@ -154,7 +151,6 @@ def test_chrono_duration_subtraction_equivalence(): def test_chrono_duration_subtraction_equivalence_date(): - date1 = datetime.date.today() date2 = datetime.date.today() diff --git a/pybind11/tests/test_class.cpp b/pybind11/tests/test_class.cpp index c8b8071dc..7241bc881 100644 --- a/pybind11/tests/test_class.cpp +++ b/pybind11/tests/test_class.cpp @@ -22,10 +22,8 @@ #include -#if defined(_MSC_VER) -# pragma warning(disable : 4324) +PYBIND11_WARNING_DISABLE_MSVC(4324) // warning C4324: structure was padded due to alignment specifier -#endif // test_brace_initialization struct NoBraceInitialization { @@ -36,7 +34,29 @@ struct NoBraceInitialization { std::vector vec; }; +namespace test_class { +namespace pr4220_tripped_over_this { // PR #4227 + +template +struct SoEmpty {}; + +template +std::string get_msg(const T &) { + return "This is really only meant to exercise successful compilation."; +} + +using Empty0 = SoEmpty<0x0>; + +void bind_empty0(py::module_ &m) { + py::class_(m, "Empty0").def(py::init<>()).def("get_msg", get_msg); +} + +} // namespace pr4220_tripped_over_this +} // namespace test_class + TEST_SUBMODULE(class_, m) { + m.def("obj_class_name", [](py::handle obj) { return py::detail::obj_class_name(obj.ptr()); }); + // test_instance struct NoConstructor { NoConstructor() = default; @@ -65,7 +85,7 @@ TEST_SUBMODULE(class_, m) { .def_static("new_instance", &NoConstructor::new_instance, "Return an instance"); py::class_(m, "NoConstructorNew") - .def(py::init([](const NoConstructorNew &self) { return self; })) // Need a NOOP __init__ + .def(py::init([]() { return nullptr; })) // Need a NOOP __init__ .def_static("__new__", [](const py::object &) { return NoConstructorNew::new_instance(); }); @@ -364,6 +384,8 @@ TEST_SUBMODULE(class_, m) { protected: virtual int foo() const { return value; } + virtual void *void_foo() { return static_cast(&value); } + virtual void *get_self() { return static_cast(this); } private: int value = 42; @@ -372,6 +394,8 @@ TEST_SUBMODULE(class_, m) { class TrampolineB : public ProtectedB { public: int foo() const override { PYBIND11_OVERRIDE(int, ProtectedB, foo, ); } + void *void_foo() override { PYBIND11_OVERRIDE(void *, ProtectedB, void_foo, ); } + void *get_self() override { PYBIND11_OVERRIDE(void *, ProtectedB, get_self, ); } }; class PublicistB : public ProtectedB { @@ -381,11 +405,23 @@ TEST_SUBMODULE(class_, m) { // (in Debug builds only, tested with icpc (ICC) 2021.1 Beta 20200827) ~PublicistB() override{}; // NOLINT(modernize-use-equals-default) using ProtectedB::foo; + using ProtectedB::get_self; + using ProtectedB::void_foo; }; + m.def("read_foo", [](const void *original) { + const int *ptr = reinterpret_cast(original); + return *ptr; + }); + + m.def("pointers_equal", + [](const void *original, const void *comparison) { return original == comparison; }); + py::class_(m, "ProtectedB") .def(py::init<>()) - .def("foo", &PublicistB::foo); + .def("foo", &PublicistB::foo) + .def("void_foo", &PublicistB::void_foo) + .def("get_self", &PublicistB::get_self); // test_brace_initialization struct BraceInitialization { @@ -517,6 +553,8 @@ TEST_SUBMODULE(class_, m) { py::class_(gt, "OtherDuplicateNested"); py::class_(gt, "YetAnotherDuplicateNested"); }); + + test_class::pr4220_tripped_over_this::bind_empty0(m); } template diff --git a/pybind11/tests/test_class.py b/pybind11/tests/test_class.py index ff9196f0f..ee7467cf8 100644 --- a/pybind11/tests/test_class.py +++ b/pybind11/tests/test_class.py @@ -1,10 +1,16 @@ import pytest -import env # noqa: F401 +import env from pybind11_tests import ConstructorStats, UserType from pybind11_tests import class_ as m +def test_obj_class_name(): + expected_name = "UserType" if env.PYPY else "pybind11_tests.UserType" + assert m.obj_class_name(UserType(1)) == expected_name + assert m.obj_class_name(UserType) == expected_name + + def test_repr(): assert "pybind11_type" in repr(type(UserType)) assert "UserType" in repr(UserType) @@ -23,7 +29,7 @@ def test_instance(msg): assert cstats.alive() == 0 -def test_instance_new(msg): +def test_instance_new(): instance = m.NoConstructorNew() # .__new__(m.NoConstructor.__class__) cstats = ConstructorStats.get(m.NoConstructorNew) assert cstats.alive() == 1 @@ -176,7 +182,6 @@ def test_inheritance(msg): def test_inheritance_init(msg): - # Single base class Python(m.Pet): def __init__(self): @@ -213,7 +218,7 @@ def test_automatic_upcasting(): def test_isinstance(): - objects = [tuple(), dict(), m.Pet("Polly", "parrot")] + [m.Dog("Molly")] * 4 + objects = [(), {}, m.Pet("Polly", "parrot")] + [m.Dog("Molly")] * 4 expected = (True, True, True, True, True, False, False) assert m.check_instances(objects) == expected @@ -313,6 +318,8 @@ def test_bind_protected_functions(): b = m.ProtectedB() assert b.foo() == 42 + assert m.read_foo(b.void_foo()) == 42 + assert m.pointers_equal(b.get_self(), b) class C(m.ProtectedB): def __init__(self): @@ -417,7 +424,7 @@ def test_exception_rvalue_abort(): # https://github.com/pybind/pybind11/issues/1568 -def test_multiple_instances_with_same_pointer(capture): +def test_multiple_instances_with_same_pointer(): n = 100 instances = [m.SamePointer() for _ in range(n)] for i in range(n): @@ -469,3 +476,10 @@ def test_register_duplicate_class(): m.register_duplicate_nested_class_type(ClassScope) expected = 'generic_type: type "YetAnotherDuplicateNested" is already registered!' assert str(exc_info.value) == expected + + +def test_pr4220_tripped_over_this(): + assert ( + m.Empty0().get_msg() + == "This is really only meant to exercise successful compilation." + ) diff --git a/pybind11/tests/test_cmake_build/CMakeLists.txt b/pybind11/tests/test_cmake_build/CMakeLists.txt index 8bfaa386a..e5aa975cf 100644 --- a/pybind11/tests/test_cmake_build/CMakeLists.txt +++ b/pybind11/tests/test_cmake_build/CMakeLists.txt @@ -1,6 +1,3 @@ -# Built-in in CMake 3.5+ -include(CMakeParseArguments) - add_custom_target(test_cmake_build) function(pybind11_add_build_test name) diff --git a/pybind11/tests/test_cmake_build/installed_embed/CMakeLists.txt b/pybind11/tests/test_cmake_build/installed_embed/CMakeLists.txt index f7d693998..d9dcb45e4 100644 --- a/pybind11/tests/test_cmake_build/installed_embed/CMakeLists.txt +++ b/pybind11/tests/test_cmake_build/installed_embed/CMakeLists.txt @@ -1,12 +1,12 @@ -cmake_minimum_required(VERSION 3.4) +cmake_minimum_required(VERSION 3.5) -# The `cmake_minimum_required(VERSION 3.4...3.18)` syntax does not work with +# The `cmake_minimum_required(VERSION 3.5...3.26)` syntax does not work with # some versions of VS that have a patched CMake 3.11. This forces us to emulate # the behavior using the following workaround: -if(${CMAKE_VERSION} VERSION_LESS 3.18) +if(${CMAKE_VERSION} VERSION_LESS 3.26) cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}) else() - cmake_policy(VERSION 3.18) + cmake_policy(VERSION 3.26) endif() project(test_installed_embed CXX) diff --git a/pybind11/tests/test_cmake_build/installed_function/CMakeLists.txt b/pybind11/tests/test_cmake_build/installed_function/CMakeLists.txt index d7ca4db55..2f4f64275 100644 --- a/pybind11/tests/test_cmake_build/installed_function/CMakeLists.txt +++ b/pybind11/tests/test_cmake_build/installed_function/CMakeLists.txt @@ -1,13 +1,13 @@ -cmake_minimum_required(VERSION 3.4) +cmake_minimum_required(VERSION 3.5) project(test_installed_module CXX) -# The `cmake_minimum_required(VERSION 3.4...3.18)` syntax does not work with +# The `cmake_minimum_required(VERSION 3.5...3.26)` syntax does not work with # some versions of VS that have a patched CMake 3.11. This forces us to emulate # the behavior using the following workaround: -if(${CMAKE_VERSION} VERSION_LESS 3.18) +if(${CMAKE_VERSION} VERSION_LESS 3.26) cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}) else() - cmake_policy(VERSION 3.18) + cmake_policy(VERSION 3.26) endif() project(test_installed_function CXX) diff --git a/pybind11/tests/test_cmake_build/installed_target/CMakeLists.txt b/pybind11/tests/test_cmake_build/installed_target/CMakeLists.txt index bc5e101f1..a981e236f 100644 --- a/pybind11/tests/test_cmake_build/installed_target/CMakeLists.txt +++ b/pybind11/tests/test_cmake_build/installed_target/CMakeLists.txt @@ -1,12 +1,12 @@ -cmake_minimum_required(VERSION 3.4) +cmake_minimum_required(VERSION 3.5) -# The `cmake_minimum_required(VERSION 3.4...3.18)` syntax does not work with +# The `cmake_minimum_required(VERSION 3.5...3.26)` syntax does not work with # some versions of VS that have a patched CMake 3.11. This forces us to emulate # the behavior using the following workaround: -if(${CMAKE_VERSION} VERSION_LESS 3.18) +if(${CMAKE_VERSION} VERSION_LESS 3.26) cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}) else() - cmake_policy(VERSION 3.18) + cmake_policy(VERSION 3.26) endif() project(test_installed_target CXX) diff --git a/pybind11/tests/test_cmake_build/subdirectory_embed/CMakeLists.txt b/pybind11/tests/test_cmake_build/subdirectory_embed/CMakeLists.txt index 58cdd7cfd..f286746b9 100644 --- a/pybind11/tests/test_cmake_build/subdirectory_embed/CMakeLists.txt +++ b/pybind11/tests/test_cmake_build/subdirectory_embed/CMakeLists.txt @@ -1,12 +1,12 @@ -cmake_minimum_required(VERSION 3.4) +cmake_minimum_required(VERSION 3.5) -# The `cmake_minimum_required(VERSION 3.4...3.18)` syntax does not work with +# The `cmake_minimum_required(VERSION 3.5...3.26)` syntax does not work with # some versions of VS that have a patched CMake 3.11. This forces us to emulate # the behavior using the following workaround: -if(${CMAKE_VERSION} VERSION_LESS 3.18) +if(${CMAKE_VERSION} VERSION_LESS 3.26) cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}) else() - cmake_policy(VERSION 3.18) + cmake_policy(VERSION 3.26) endif() project(test_subdirectory_embed CXX) diff --git a/pybind11/tests/test_cmake_build/subdirectory_function/CMakeLists.txt b/pybind11/tests/test_cmake_build/subdirectory_function/CMakeLists.txt index 01557c439..275a75c0b 100644 --- a/pybind11/tests/test_cmake_build/subdirectory_function/CMakeLists.txt +++ b/pybind11/tests/test_cmake_build/subdirectory_function/CMakeLists.txt @@ -1,12 +1,12 @@ -cmake_minimum_required(VERSION 3.4) +cmake_minimum_required(VERSION 3.5) -# The `cmake_minimum_required(VERSION 3.4...3.18)` syntax does not work with +# The `cmake_minimum_required(VERSION 3.5...3.26)` syntax does not work with # some versions of VS that have a patched CMake 3.11. This forces us to emulate # the behavior using the following workaround: -if(${CMAKE_VERSION} VERSION_LESS 3.18) +if(${CMAKE_VERSION} VERSION_LESS 3.26) cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}) else() - cmake_policy(VERSION 3.18) + cmake_policy(VERSION 3.26) endif() project(test_subdirectory_function CXX) diff --git a/pybind11/tests/test_cmake_build/subdirectory_target/CMakeLists.txt b/pybind11/tests/test_cmake_build/subdirectory_target/CMakeLists.txt index ba82fdee2..37bb2c56e 100644 --- a/pybind11/tests/test_cmake_build/subdirectory_target/CMakeLists.txt +++ b/pybind11/tests/test_cmake_build/subdirectory_target/CMakeLists.txt @@ -1,12 +1,12 @@ -cmake_minimum_required(VERSION 3.4) +cmake_minimum_required(VERSION 3.5) -# The `cmake_minimum_required(VERSION 3.4...3.18)` syntax does not work with +# The `cmake_minimum_required(VERSION 3.5...3.26)` syntax does not work with # some versions of VS that have a patched CMake 3.11. This forces us to emulate # the behavior using the following workaround: -if(${CMAKE_VERSION} VERSION_LESS 3.18) +if(${CMAKE_VERSION} VERSION_LESS 3.26) cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}) else() - cmake_policy(VERSION 3.18) + cmake_policy(VERSION 3.26) endif() project(test_subdirectory_target CXX) diff --git a/pybind11/tests/test_const_name.py b/pybind11/tests/test_const_name.py index 10b0caee2..a145f0bbb 100644 --- a/pybind11/tests/test_const_name.py +++ b/pybind11/tests/test_const_name.py @@ -3,9 +3,9 @@ import pytest from pybind11_tests import const_name as m -@pytest.mark.parametrize("func", (m.const_name_tests, m.underscore_tests)) +@pytest.mark.parametrize("func", [m.const_name_tests, m.underscore_tests]) @pytest.mark.parametrize( - "selector, expected", + ("selector", "expected"), enumerate( ( "", diff --git a/pybind11/tests/test_constants_and_functions.cpp b/pybind11/tests/test_constants_and_functions.cpp index 1918a429c..312edca9e 100644 --- a/pybind11/tests/test_constants_and_functions.cpp +++ b/pybind11/tests/test_constants_and_functions.cpp @@ -52,15 +52,12 @@ int f1(int x) noexcept { return x + 1; } #endif int f2(int x) noexcept(true) { return x + 2; } int f3(int x) noexcept(false) { return x + 3; } -#if defined(__GNUG__) && !defined(__INTEL_COMPILER) -# pragma GCC diagnostic push -# pragma GCC diagnostic ignored "-Wdeprecated" -#endif +PYBIND11_WARNING_PUSH +PYBIND11_WARNING_DISABLE_GCC("-Wdeprecated") +PYBIND11_WARNING_DISABLE_CLANG("-Wdeprecated") // NOLINTNEXTLINE(modernize-use-noexcept) int f4(int x) throw() { return x + 4; } // Deprecated equivalent to noexcept(true) -#if defined(__GNUG__) && !defined(__INTEL_COMPILER) -# pragma GCC diagnostic pop -#endif +PYBIND11_WARNING_POP struct C { int m1(int x) noexcept { return x - 1; } int m2(int x) const noexcept { return x - 2; } @@ -68,17 +65,14 @@ struct C { int m4(int x) const noexcept(true) { return x - 4; } int m5(int x) noexcept(false) { return x - 5; } int m6(int x) const noexcept(false) { return x - 6; } -#if defined(__GNUG__) && !defined(__INTEL_COMPILER) -# pragma GCC diagnostic push -# pragma GCC diagnostic ignored "-Wdeprecated" -#endif + PYBIND11_WARNING_PUSH + PYBIND11_WARNING_DISABLE_GCC("-Wdeprecated") + PYBIND11_WARNING_DISABLE_CLANG("-Wdeprecated") // NOLINTNEXTLINE(modernize-use-noexcept) int m7(int x) throw() { return x - 7; } // NOLINTNEXTLINE(modernize-use-noexcept) int m8(int x) const throw() { return x - 8; } -#if defined(__GNUG__) && !defined(__INTEL_COMPILER) -# pragma GCC diagnostic pop -#endif + PYBIND11_WARNING_POP }; } // namespace test_exc_sp @@ -126,14 +120,12 @@ TEST_SUBMODULE(constants_and_functions, m) { .def("m8", &C::m8); m.def("f1", f1); m.def("f2", f2); -#if defined(__INTEL_COMPILER) -# pragma warning push -# pragma warning disable 878 // incompatible exception specifications -#endif + + PYBIND11_WARNING_PUSH + PYBIND11_WARNING_DISABLE_INTEL(878) // incompatible exception specifications m.def("f3", f3); -#if defined(__INTEL_COMPILER) -# pragma warning pop -#endif + PYBIND11_WARNING_POP + m.def("f4", f4); // test_function_record_leaks @@ -156,4 +148,7 @@ TEST_SUBMODULE(constants_and_functions, m) { py::arg_v("y", 42, ""), py::arg_v("z", default_value)); }); + + // test noexcept(true) lambda (#4565) + m.def("l1", []() noexcept(true) { return 0; }); } diff --git a/pybind11/tests/test_constants_and_functions.py b/pybind11/tests/test_constants_and_functions.py index 5da0b84b8..a1142461c 100644 --- a/pybind11/tests/test_constants_and_functions.py +++ b/pybind11/tests/test_constants_and_functions.py @@ -50,3 +50,7 @@ def test_function_record_leaks(): m.register_large_capture_with_invalid_arguments(m) with pytest.raises(RuntimeError): m.register_with_raising_repr(m, RaisingRepr()) + + +def test_noexcept_lambda(): + assert m.l1() == 0 diff --git a/pybind11/tests/test_copy_move.cpp b/pybind11/tests/test_copy_move.cpp index 28c244564..f54733550 100644 --- a/pybind11/tests/test_copy_move.cpp +++ b/pybind11/tests/test_copy_move.cpp @@ -13,6 +13,8 @@ #include "constructor_stats.h" #include "pybind11_tests.h" +#include + template struct empty { static const derived &get_one() { return instance_; } @@ -293,3 +295,239 @@ TEST_SUBMODULE(copy_move_policies, m) { // Make sure that cast from pytype rvalue to other pytype works m.def("get_pytype_rvalue_castissue", [](double i) { return py::float_(i).cast(); }); } + +/* + * Rest of the file: + * static_assert based tests for pybind11 adaptations of + * std::is_move_constructible, std::is_copy_constructible and + * std::is_copy_assignable (no adaptation of std::is_move_assignable). + * Difference between pybind11 and std traits: pybind11 traits will also check + * the contained value_types. + */ + +struct NotMovable { + NotMovable() = default; + NotMovable(NotMovable const &) = default; + NotMovable(NotMovable &&) = delete; + NotMovable &operator=(NotMovable const &) = default; + NotMovable &operator=(NotMovable &&) = delete; +}; +static_assert(!std::is_move_constructible::value, + "!std::is_move_constructible::value"); +static_assert(std::is_copy_constructible::value, + "std::is_copy_constructible::value"); +static_assert(!pybind11::detail::is_move_constructible::value, + "!pybind11::detail::is_move_constructible::value"); +static_assert(pybind11::detail::is_copy_constructible::value, + "pybind11::detail::is_copy_constructible::value"); +static_assert(!std::is_move_assignable::value, + "!std::is_move_assignable::value"); +static_assert(std::is_copy_assignable::value, + "std::is_copy_assignable::value"); +// pybind11 does not have this +// static_assert(!pybind11::detail::is_move_assignable::value, +// "!pybind11::detail::is_move_assignable::value"); +static_assert(pybind11::detail::is_copy_assignable::value, + "pybind11::detail::is_copy_assignable::value"); + +struct NotCopyable { + NotCopyable() = default; + NotCopyable(NotCopyable const &) = delete; + NotCopyable(NotCopyable &&) = default; + NotCopyable &operator=(NotCopyable const &) = delete; + NotCopyable &operator=(NotCopyable &&) = default; +}; +static_assert(std::is_move_constructible::value, + "std::is_move_constructible::value"); +static_assert(!std::is_copy_constructible::value, + "!std::is_copy_constructible::value"); +static_assert(pybind11::detail::is_move_constructible::value, + "pybind11::detail::is_move_constructible::value"); +static_assert(!pybind11::detail::is_copy_constructible::value, + "!pybind11::detail::is_copy_constructible::value"); +static_assert(std::is_move_assignable::value, + "std::is_move_assignable::value"); +static_assert(!std::is_copy_assignable::value, + "!std::is_copy_assignable::value"); +// pybind11 does not have this +// static_assert(!pybind11::detail::is_move_assignable::value, +// "!pybind11::detail::is_move_assignable::value"); +static_assert(!pybind11::detail::is_copy_assignable::value, + "!pybind11::detail::is_copy_assignable::value"); + +struct NotCopyableNotMovable { + NotCopyableNotMovable() = default; + NotCopyableNotMovable(NotCopyableNotMovable const &) = delete; + NotCopyableNotMovable(NotCopyableNotMovable &&) = delete; + NotCopyableNotMovable &operator=(NotCopyableNotMovable const &) = delete; + NotCopyableNotMovable &operator=(NotCopyableNotMovable &&) = delete; +}; +static_assert(!std::is_move_constructible::value, + "!std::is_move_constructible::value"); +static_assert(!std::is_copy_constructible::value, + "!std::is_copy_constructible::value"); +static_assert(!pybind11::detail::is_move_constructible::value, + "!pybind11::detail::is_move_constructible::value"); +static_assert(!pybind11::detail::is_copy_constructible::value, + "!pybind11::detail::is_copy_constructible::value"); +static_assert(!std::is_move_assignable::value, + "!std::is_move_assignable::value"); +static_assert(!std::is_copy_assignable::value, + "!std::is_copy_assignable::value"); +// pybind11 does not have this +// static_assert(!pybind11::detail::is_move_assignable::value, +// "!pybind11::detail::is_move_assignable::value"); +static_assert(!pybind11::detail::is_copy_assignable::value, + "!pybind11::detail::is_copy_assignable::value"); + +struct NotMovableVector : std::vector {}; +static_assert(std::is_move_constructible::value, + "std::is_move_constructible::value"); +static_assert(std::is_copy_constructible::value, + "std::is_copy_constructible::value"); +static_assert(!pybind11::detail::is_move_constructible::value, + "!pybind11::detail::is_move_constructible::value"); +static_assert(pybind11::detail::is_copy_constructible::value, + "pybind11::detail::is_copy_constructible::value"); +static_assert(std::is_move_assignable::value, + "std::is_move_assignable::value"); +static_assert(std::is_copy_assignable::value, + "std::is_copy_assignable::value"); +// pybind11 does not have this +// static_assert(!pybind11::detail::is_move_assignable::value, +// "!pybind11::detail::is_move_assignable::value"); +static_assert(pybind11::detail::is_copy_assignable::value, + "pybind11::detail::is_copy_assignable::value"); + +struct NotCopyableVector : std::vector {}; +static_assert(std::is_move_constructible::value, + "std::is_move_constructible::value"); +static_assert(std::is_copy_constructible::value, + "std::is_copy_constructible::value"); +static_assert(pybind11::detail::is_move_constructible::value, + "pybind11::detail::is_move_constructible::value"); +static_assert(!pybind11::detail::is_copy_constructible::value, + "!pybind11::detail::is_copy_constructible::value"); +static_assert(std::is_move_assignable::value, + "std::is_move_assignable::value"); +static_assert(std::is_copy_assignable::value, + "std::is_copy_assignable::value"); +// pybind11 does not have this +// static_assert(!pybind11::detail::is_move_assignable::value, +// "!pybind11::detail::is_move_assignable::value"); +static_assert(!pybind11::detail::is_copy_assignable::value, + "!pybind11::detail::is_copy_assignable::value"); + +struct NotCopyableNotMovableVector : std::vector {}; +static_assert(std::is_move_constructible::value, + "std::is_move_constructible::value"); +static_assert(std::is_copy_constructible::value, + "std::is_copy_constructible::value"); +static_assert(!pybind11::detail::is_move_constructible::value, + "!pybind11::detail::is_move_constructible::value"); +static_assert(!pybind11::detail::is_copy_constructible::value, + "!pybind11::detail::is_copy_constructible::value"); +static_assert(std::is_move_assignable::value, + "std::is_move_assignable::value"); +static_assert(std::is_copy_assignable::value, + "std::is_copy_assignable::value"); +// pybind11 does not have this +// static_assert(!pybind11::detail::is_move_assignable::value, +// "!pybind11::detail::is_move_assignable::value"); +static_assert(!pybind11::detail::is_copy_assignable::value, + "!pybind11::detail::is_copy_assignable::value"); + +struct NotMovableMap : std::map {}; +static_assert(std::is_move_constructible::value, + "std::is_move_constructible::value"); +static_assert(std::is_copy_constructible::value, + "std::is_copy_constructible::value"); +static_assert(!pybind11::detail::is_move_constructible::value, + "!pybind11::detail::is_move_constructible::value"); +static_assert(pybind11::detail::is_copy_constructible::value, + "pybind11::detail::is_copy_constructible::value"); +static_assert(std::is_move_assignable::value, + "std::is_move_assignable::value"); +static_assert(std::is_copy_assignable::value, + "std::is_copy_assignable::value"); +// pybind11 does not have this +// static_assert(!pybind11::detail::is_move_assignable::value, +// "!pybind11::detail::is_move_assignable::value"); +static_assert(pybind11::detail::is_copy_assignable::value, + "pybind11::detail::is_copy_assignable::value"); + +struct NotCopyableMap : std::map {}; +static_assert(std::is_move_constructible::value, + "std::is_move_constructible::value"); +static_assert(std::is_copy_constructible::value, + "std::is_copy_constructible::value"); +static_assert(pybind11::detail::is_move_constructible::value, + "pybind11::detail::is_move_constructible::value"); +static_assert(!pybind11::detail::is_copy_constructible::value, + "!pybind11::detail::is_copy_constructible::value"); +static_assert(std::is_move_assignable::value, + "std::is_move_assignable::value"); +static_assert(std::is_copy_assignable::value, + "std::is_copy_assignable::value"); +// pybind11 does not have this +// static_assert(!pybind11::detail::is_move_assignable::value, +// "!pybind11::detail::is_move_assignable::value"); +static_assert(!pybind11::detail::is_copy_assignable::value, + "!pybind11::detail::is_copy_assignable::value"); + +struct NotCopyableNotMovableMap : std::map {}; +static_assert(std::is_move_constructible::value, + "std::is_move_constructible::value"); +static_assert(std::is_copy_constructible::value, + "std::is_copy_constructible::value"); +static_assert(!pybind11::detail::is_move_constructible::value, + "!pybind11::detail::is_move_constructible::value"); +static_assert(!pybind11::detail::is_copy_constructible::value, + "!pybind11::detail::is_copy_constructible::value"); +static_assert(std::is_move_assignable::value, + "std::is_move_assignable::value"); +static_assert(std::is_copy_assignable::value, + "std::is_copy_assignable::value"); +// pybind11 does not have this +// static_assert(!pybind11::detail::is_move_assignable::value, +// "!pybind11::detail::is_move_assignable::value"); +static_assert(!pybind11::detail::is_copy_assignable::value, + "!pybind11::detail::is_copy_assignable::value"); + +struct RecursiveVector : std::vector {}; +static_assert(std::is_move_constructible::value, + "std::is_move_constructible::value"); +static_assert(std::is_copy_constructible::value, + "std::is_copy_constructible::value"); +static_assert(pybind11::detail::is_move_constructible::value, + "pybind11::detail::is_move_constructible::value"); +static_assert(pybind11::detail::is_copy_constructible::value, + "pybind11::detail::is_copy_constructible::value"); +static_assert(std::is_move_assignable::value, + "std::is_move_assignable::value"); +static_assert(std::is_copy_assignable::value, + "std::is_copy_assignable::value"); +// pybind11 does not have this +// static_assert(!pybind11::detail::is_move_assignable::value, +// "!pybind11::detail::is_move_assignable::value"); +static_assert(pybind11::detail::is_copy_assignable::value, + "pybind11::detail::is_copy_assignable::value"); + +struct RecursiveMap : std::map {}; +static_assert(std::is_move_constructible::value, + "std::is_move_constructible::value"); +static_assert(std::is_copy_constructible::value, + "std::is_copy_constructible::value"); +static_assert(pybind11::detail::is_move_constructible::value, + "pybind11::detail::is_move_constructible::value"); +static_assert(pybind11::detail::is_copy_constructible::value, + "pybind11::detail::is_copy_constructible::value"); +static_assert(std::is_move_assignable::value, + "std::is_move_assignable::value"); +static_assert(std::is_copy_assignable::value, + "std::is_copy_assignable::value"); +// pybind11 does not have this +// static_assert(!pybind11::detail::is_move_assignable::value, +// "!pybind11::detail::is_move_assignable::value"); +static_assert(pybind11::detail::is_copy_assignable::value, + "pybind11::detail::is_copy_assignable::value"); diff --git a/pybind11/tests/test_custom_type_casters.cpp b/pybind11/tests/test_custom_type_casters.cpp index 25540e368..b4af02a45 100644 --- a/pybind11/tests/test_custom_type_casters.cpp +++ b/pybind11/tests/test_custom_type_casters.cpp @@ -21,7 +21,7 @@ public: }; class ArgAlwaysConverts {}; -namespace pybind11 { +namespace PYBIND11_NAMESPACE { namespace detail { template <> struct type_caster { @@ -74,7 +74,7 @@ public: } }; } // namespace detail -} // namespace pybind11 +} // namespace PYBIND11_NAMESPACE // test_custom_caster_destruction class DestructionTester { @@ -92,7 +92,7 @@ public: return *this; } }; -namespace pybind11 { +namespace PYBIND11_NAMESPACE { namespace detail { template <> struct type_caster { @@ -104,7 +104,7 @@ struct type_caster { } }; } // namespace detail -} // namespace pybind11 +} // namespace PYBIND11_NAMESPACE // Define type caster outside of `pybind11::detail` and then alias it. namespace other_lib { @@ -112,7 +112,7 @@ struct MyType {}; // Corrupt `py` shorthand alias for surrounding context. namespace py {} // Corrupt unqualified relative `pybind11` namespace. -namespace pybind11 {} +namespace PYBIND11_NAMESPACE {} // Correct alias. namespace py_ = ::pybind11; // Define caster. This is effectively no-op, we only ensure it compiles and we @@ -127,12 +127,12 @@ struct my_caster { }; } // namespace other_lib // Effectively "alias" it into correct namespace (via inheritance). -namespace pybind11 { +namespace PYBIND11_NAMESPACE { namespace detail { template <> struct type_caster : public other_lib::my_caster {}; } // namespace detail -} // namespace pybind11 +} // namespace PYBIND11_NAMESPACE TEST_SUBMODULE(custom_type_casters, m) { // test_custom_type_casters diff --git a/pybind11/tests/test_custom_type_casters.py b/pybind11/tests/test_custom_type_casters.py index adfa6cf86..3a00ea964 100644 --- a/pybind11/tests/test_custom_type_casters.py +++ b/pybind11/tests/test_custom_type_casters.py @@ -94,12 +94,14 @@ def test_noconvert_args(msg): def test_custom_caster_destruction(): """Tests that returning a pointer to a type that gets converted with a custom type caster gets - destroyed when the function has py::return_value_policy::take_ownership policy applied.""" + destroyed when the function has py::return_value_policy::take_ownership policy applied. + """ cstats = m.destruction_tester_cstats() # This one *doesn't* have take_ownership: the pointer should be used but not destroyed: z = m.custom_caster_no_destroy() - assert cstats.alive() == 1 and cstats.default_constructions == 1 + assert cstats.alive() == 1 + assert cstats.default_constructions == 1 assert z # take_ownership applied: this constructs a new object, casts it, then destroys it: diff --git a/pybind11/tests/test_custom_type_setup.py b/pybind11/tests/test_custom_type_setup.py index 19b44c9de..e63ff5758 100644 --- a/pybind11/tests/test_custom_type_setup.py +++ b/pybind11/tests/test_custom_type_setup.py @@ -7,7 +7,7 @@ import env # noqa: F401 from pybind11_tests import custom_type_setup as m -@pytest.fixture +@pytest.fixture() def gc_tester(): """Tests that an object is garbage collected. diff --git a/pybind11/tests/test_docstring_options.cpp b/pybind11/tests/test_docstring_options.cpp index 4d44f4e20..dda1cf6e4 100644 --- a/pybind11/tests/test_docstring_options.cpp +++ b/pybind11/tests/test_docstring_options.cpp @@ -85,4 +85,57 @@ TEST_SUBMODULE(docstring_options, m) { &DocstringTestFoo::setValue, "This is a property docstring"); } + + { + enum class DocstringTestEnum1 { Member1, Member2 }; + + py::enum_(m, "DocstringTestEnum1", "Enum docstring") + .value("Member1", DocstringTestEnum1::Member1) + .value("Member2", DocstringTestEnum1::Member2); + } + + { + py::options options; + options.enable_enum_members_docstring(); + + enum class DocstringTestEnum2 { Member1, Member2 }; + + py::enum_(m, "DocstringTestEnum2", "Enum docstring") + .value("Member1", DocstringTestEnum2::Member1) + .value("Member2", DocstringTestEnum2::Member2); + } + + { + py::options options; + options.disable_enum_members_docstring(); + + enum class DocstringTestEnum3 { Member1, Member2 }; + + py::enum_(m, "DocstringTestEnum3", "Enum docstring") + .value("Member1", DocstringTestEnum3::Member1) + .value("Member2", DocstringTestEnum3::Member2); + } + + { + py::options options; + options.disable_user_defined_docstrings(); + + enum class DocstringTestEnum4 { Member1, Member2 }; + + py::enum_(m, "DocstringTestEnum4", "Enum docstring") + .value("Member1", DocstringTestEnum4::Member1) + .value("Member2", DocstringTestEnum4::Member2); + } + + { + py::options options; + options.disable_user_defined_docstrings(); + options.disable_enum_members_docstring(); + + enum class DocstringTestEnum5 { Member1, Member2 }; + + py::enum_(m, "DocstringTestEnum5", "Enum docstring") + .value("Member1", DocstringTestEnum5::Member1) + .value("Member2", DocstringTestEnum5::Member2); + } } diff --git a/pybind11/tests/test_docstring_options.py b/pybind11/tests/test_docstring_options.py index fcd16b89f..e6f5a9d98 100644 --- a/pybind11/tests/test_docstring_options.py +++ b/pybind11/tests/test_docstring_options.py @@ -39,3 +39,26 @@ def test_docstring_options(): # Suppression of user-defined docstrings for non-function objects assert not m.DocstringTestFoo.__doc__ assert not m.DocstringTestFoo.value_prop.__doc__ + + # Check existig behaviour of enum docstings + assert ( + m.DocstringTestEnum1.__doc__ + == "Enum docstring\n\nMembers:\n\n Member1\n\n Member2" + ) + + # options.enable_enum_members_docstring() + assert ( + m.DocstringTestEnum2.__doc__ + == "Enum docstring\n\nMembers:\n\n Member1\n\n Member2" + ) + + # options.disable_enum_members_docstring() + assert m.DocstringTestEnum3.__doc__ == "Enum docstring" + + # options.disable_user_defined_docstrings() + assert m.DocstringTestEnum4.__doc__ == "Members:\n\n Member1\n\n Member2" + + # options.disable_user_defined_docstrings() + # options.disable_enum_members_docstring() + # When all options are disabled, no docstring (instead of an empty one) should be generated + assert m.DocstringTestEnum5.__doc__ is None diff --git a/pybind11/tests/test_eigen_matrix.cpp b/pybind11/tests/test_eigen_matrix.cpp new file mode 100644 index 000000000..554cc4d7f --- /dev/null +++ b/pybind11/tests/test_eigen_matrix.cpp @@ -0,0 +1,428 @@ +/* + tests/eigen.cpp -- automatic conversion of Eigen types + + Copyright (c) 2016 Wenzel Jakob + + All rights reserved. Use of this source code is governed by a + BSD-style license that can be found in the LICENSE file. +*/ + +#include +#include + +#include "constructor_stats.h" +#include "pybind11_tests.h" + +PYBIND11_WARNING_DISABLE_MSVC(4996) + +#include + +using MatrixXdR = Eigen::Matrix; + +// Sets/resets a testing reference matrix to have values of 10*r + c, where r and c are the +// (1-based) row/column number. +template +void reset_ref(M &x) { + for (int i = 0; i < x.rows(); i++) { + for (int j = 0; j < x.cols(); j++) { + x(i, j) = 11 + 10 * i + j; + } + } +} + +// Returns a static, column-major matrix +Eigen::MatrixXd &get_cm() { + static Eigen::MatrixXd *x; + if (!x) { + x = new Eigen::MatrixXd(3, 3); + reset_ref(*x); + } + return *x; +} +// Likewise, but row-major +MatrixXdR &get_rm() { + static MatrixXdR *x; + if (!x) { + x = new MatrixXdR(3, 3); + reset_ref(*x); + } + return *x; +} +// Resets the values of the static matrices returned by get_cm()/get_rm() +void reset_refs() { + reset_ref(get_cm()); + reset_ref(get_rm()); +} + +// Returns element 2,1 from a matrix (used to test copy/nocopy) +double get_elem(const Eigen::Ref &m) { return m(2, 1); }; + +// Returns a matrix with 10*r + 100*c added to each matrix element (to help test that the matrix +// reference is referencing rows/columns correctly). +template +Eigen::MatrixXd adjust_matrix(MatrixArgType m) { + Eigen::MatrixXd ret(m); + for (int c = 0; c < m.cols(); c++) { + for (int r = 0; r < m.rows(); r++) { + ret(r, c) += 10 * r + 100 * c; // NOLINT(clang-analyzer-core.uninitialized.Assign) + } + } + return ret; +} + +struct CustomOperatorNew { + CustomOperatorNew() = default; + + Eigen::Matrix4d a = Eigen::Matrix4d::Zero(); + Eigen::Matrix4d b = Eigen::Matrix4d::Identity(); + + EIGEN_MAKE_ALIGNED_OPERATOR_NEW; +}; + +TEST_SUBMODULE(eigen_matrix, m) { + using FixedMatrixR = Eigen::Matrix; + using FixedMatrixC = Eigen::Matrix; + using DenseMatrixR = Eigen::Matrix; + using DenseMatrixC = Eigen::Matrix; + using FourRowMatrixC = Eigen::Matrix; + using FourColMatrixC = Eigen::Matrix; + using FourRowMatrixR = Eigen::Matrix; + using FourColMatrixR = Eigen::Matrix; + using SparseMatrixR = Eigen::SparseMatrix; + using SparseMatrixC = Eigen::SparseMatrix; + + // various tests + m.def("double_col", [](const Eigen::VectorXf &x) -> Eigen::VectorXf { return 2.0f * x; }); + m.def("double_row", + [](const Eigen::RowVectorXf &x) -> Eigen::RowVectorXf { return 2.0f * x; }); + m.def("double_complex", + [](const Eigen::VectorXcf &x) -> Eigen::VectorXcf { return 2.0f * x; }); + m.def("double_threec", [](py::EigenDRef x) { x *= 2; }); + m.def("double_threer", [](py::EigenDRef x) { x *= 2; }); + m.def("double_mat_cm", [](const Eigen::MatrixXf &x) -> Eigen::MatrixXf { return 2.0f * x; }); + m.def("double_mat_rm", [](const DenseMatrixR &x) -> DenseMatrixR { return 2.0f * x; }); + + // test_eigen_ref_to_python + // Different ways of passing via Eigen::Ref; the first and second are the Eigen-recommended + m.def("cholesky1", + [](const Eigen::Ref &x) -> Eigen::MatrixXd { return x.llt().matrixL(); }); + m.def("cholesky2", [](const Eigen::Ref &x) -> Eigen::MatrixXd { + return x.llt().matrixL(); + }); + m.def("cholesky3", + [](const Eigen::Ref &x) -> Eigen::MatrixXd { return x.llt().matrixL(); }); + m.def("cholesky4", [](const Eigen::Ref &x) -> Eigen::MatrixXd { + return x.llt().matrixL(); + }); + + // test_eigen_ref_mutators + // Mutators: these add some value to the given element using Eigen, but Eigen should be mapping + // into the numpy array data and so the result should show up there. There are three versions: + // one that works on a contiguous-row matrix (numpy's default), one for a contiguous-column + // matrix, and one for any matrix. + auto add_rm = [](Eigen::Ref x, int r, int c, double v) { x(r, c) += v; }; + auto add_cm = [](Eigen::Ref x, int r, int c, double v) { x(r, c) += v; }; + + // Mutators (Eigen maps into numpy variables): + m.def("add_rm", add_rm); // Only takes row-contiguous + m.def("add_cm", add_cm); // Only takes column-contiguous + // Overloaded versions that will accept either row or column contiguous: + m.def("add1", add_rm); + m.def("add1", add_cm); + m.def("add2", add_cm); + m.def("add2", add_rm); + // This one accepts a matrix of any stride: + m.def("add_any", + [](py::EigenDRef x, int r, int c, double v) { x(r, c) += v; }); + + // Return mutable references (numpy maps into eigen variables) + m.def("get_cm_ref", []() { return Eigen::Ref(get_cm()); }); + m.def("get_rm_ref", []() { return Eigen::Ref(get_rm()); }); + // The same references, but non-mutable (numpy maps into eigen variables, but is !writeable) + m.def("get_cm_const_ref", []() { return Eigen::Ref(get_cm()); }); + m.def("get_rm_const_ref", []() { return Eigen::Ref(get_rm()); }); + + m.def("reset_refs", reset_refs); // Restores get_{cm,rm}_ref to original values + + // Increments and returns ref to (same) matrix + m.def( + "incr_matrix", + [](Eigen::Ref m, double v) { + m += Eigen::MatrixXd::Constant(m.rows(), m.cols(), v); + return m; + }, + py::return_value_policy::reference); + + // Same, but accepts a matrix of any strides + m.def( + "incr_matrix_any", + [](py::EigenDRef m, double v) { + m += Eigen::MatrixXd::Constant(m.rows(), m.cols(), v); + return m; + }, + py::return_value_policy::reference); + + // Returns an eigen slice of even rows + m.def( + "even_rows", + [](py::EigenDRef m) { + return py::EigenDMap( + m.data(), + (m.rows() + 1) / 2, + m.cols(), + py::EigenDStride(m.outerStride(), 2 * m.innerStride())); + }, + py::return_value_policy::reference); + + // Returns an eigen slice of even columns + m.def( + "even_cols", + [](py::EigenDRef m) { + return py::EigenDMap( + m.data(), + m.rows(), + (m.cols() + 1) / 2, + py::EigenDStride(2 * m.outerStride(), m.innerStride())); + }, + py::return_value_policy::reference); + + // Returns diagonals: a vector-like object with an inner stride != 1 + m.def("diagonal", [](const Eigen::Ref &x) { return x.diagonal(); }); + m.def("diagonal_1", + [](const Eigen::Ref &x) { return x.diagonal<1>(); }); + m.def("diagonal_n", + [](const Eigen::Ref &x, int index) { return x.diagonal(index); }); + + // Return a block of a matrix (gives non-standard strides) + m.def("block", + [m](const py::object &x_obj, + int start_row, + int start_col, + int block_rows, + int block_cols) { + return m.attr("_block")(x_obj, x_obj, start_row, start_col, block_rows, block_cols); + }); + + m.def( + "_block", + [](const py::object &x_obj, + const Eigen::Ref &x, + int start_row, + int start_col, + int block_rows, + int block_cols) { + // See PR #4217 for background. This test is a bit over the top, but might be useful + // as a concrete example to point to when explaining the dangling reference trap. + auto i0 = py::make_tuple(0, 0); + auto x0_orig = x_obj[*i0].cast(); + if (x(0, 0) != x0_orig) { + throw std::runtime_error( + "Something in the type_caster for Eigen::Ref is terribly wrong."); + } + double x0_mod = x0_orig + 1; + x_obj[*i0] = x0_mod; + auto copy_detected = (x(0, 0) != x0_mod); + x_obj[*i0] = x0_orig; + if (copy_detected) { + throw std::runtime_error("type_caster for Eigen::Ref made a copy."); + } + return x.block(start_row, start_col, block_rows, block_cols); + }, + py::keep_alive<0, 1>()); + + // test_eigen_return_references, test_eigen_keepalive + // return value referencing/copying tests: + class ReturnTester { + Eigen::MatrixXd mat = create(); + + public: + ReturnTester() { print_created(this); } + ~ReturnTester() { print_destroyed(this); } + static Eigen::MatrixXd create() { return Eigen::MatrixXd::Ones(10, 10); } + // NOLINTNEXTLINE(readability-const-return-type) + static const Eigen::MatrixXd createConst() { return Eigen::MatrixXd::Ones(10, 10); } + Eigen::MatrixXd &get() { return mat; } + Eigen::MatrixXd *getPtr() { return &mat; } + const Eigen::MatrixXd &view() { return mat; } + const Eigen::MatrixXd *viewPtr() { return &mat; } + Eigen::Ref ref() { return mat; } + Eigen::Ref refConst() { return mat; } + Eigen::Block block(int r, int c, int nrow, int ncol) { + return mat.block(r, c, nrow, ncol); + } + Eigen::Block blockConst(int r, int c, int nrow, int ncol) const { + return mat.block(r, c, nrow, ncol); + } + py::EigenDMap corners() { + return py::EigenDMap( + mat.data(), + py::EigenDStride(mat.outerStride() * (mat.outerSize() - 1), + mat.innerStride() * (mat.innerSize() - 1))); + } + py::EigenDMap cornersConst() const { + return py::EigenDMap( + mat.data(), + py::EigenDStride(mat.outerStride() * (mat.outerSize() - 1), + mat.innerStride() * (mat.innerSize() - 1))); + } + }; + using rvp = py::return_value_policy; + py::class_(m, "ReturnTester") + .def(py::init<>()) + .def_static("create", &ReturnTester::create) + .def_static("create_const", &ReturnTester::createConst) + .def("get", &ReturnTester::get, rvp::reference_internal) + .def("get_ptr", &ReturnTester::getPtr, rvp::reference_internal) + .def("view", &ReturnTester::view, rvp::reference_internal) + .def("view_ptr", &ReturnTester::view, rvp::reference_internal) + .def("copy_get", &ReturnTester::get) // Default rvp: copy + .def("copy_view", &ReturnTester::view) // " + .def("ref", &ReturnTester::ref) // Default for Ref is to reference + .def("ref_const", &ReturnTester::refConst) // Likewise, but const + .def("ref_safe", &ReturnTester::ref, rvp::reference_internal) + .def("ref_const_safe", &ReturnTester::refConst, rvp::reference_internal) + .def("copy_ref", &ReturnTester::ref, rvp::copy) + .def("copy_ref_const", &ReturnTester::refConst, rvp::copy) + .def("block", &ReturnTester::block) + .def("block_safe", &ReturnTester::block, rvp::reference_internal) + .def("block_const", &ReturnTester::blockConst, rvp::reference_internal) + .def("copy_block", &ReturnTester::block, rvp::copy) + .def("corners", &ReturnTester::corners, rvp::reference_internal) + .def("corners_const", &ReturnTester::cornersConst, rvp::reference_internal); + + // test_special_matrix_objects + // Returns a DiagonalMatrix with diagonal (1,2,3,...) + m.def("incr_diag", [](int k) { + Eigen::DiagonalMatrix m(k); + for (int i = 0; i < k; i++) { + m.diagonal()[i] = i + 1; + } + return m; + }); + + // Returns a SelfAdjointView referencing the lower triangle of m + m.def("symmetric_lower", + [](const Eigen::MatrixXi &m) { return m.selfadjointView(); }); + // Returns a SelfAdjointView referencing the lower triangle of m + m.def("symmetric_upper", + [](const Eigen::MatrixXi &m) { return m.selfadjointView(); }); + + // Test matrix for various functions below. + Eigen::MatrixXf mat(5, 6); + mat << 0, 3, 0, 0, 0, 11, 22, 0, 0, 0, 17, 11, 7, 5, 0, 1, 0, 11, 0, 0, 0, 0, 0, 11, 0, 0, 14, + 0, 8, 11; + + // test_fixed, and various other tests + m.def("fixed_r", [mat]() -> FixedMatrixR { return FixedMatrixR(mat); }); + // Our Eigen does a hack which respects constness through the numpy writeable flag. + // Therefore, the const return actually affects this type despite being an rvalue. + // NOLINTNEXTLINE(readability-const-return-type) + m.def("fixed_r_const", [mat]() -> const FixedMatrixR { return FixedMatrixR(mat); }); + m.def("fixed_c", [mat]() -> FixedMatrixC { return FixedMatrixC(mat); }); + m.def("fixed_copy_r", [](const FixedMatrixR &m) -> FixedMatrixR { return m; }); + m.def("fixed_copy_c", [](const FixedMatrixC &m) -> FixedMatrixC { return m; }); + // test_mutator_descriptors + m.def("fixed_mutator_r", [](const Eigen::Ref &) {}); + m.def("fixed_mutator_c", [](const Eigen::Ref &) {}); + m.def("fixed_mutator_a", [](const py::EigenDRef &) {}); + // test_dense + m.def("dense_r", [mat]() -> DenseMatrixR { return DenseMatrixR(mat); }); + m.def("dense_c", [mat]() -> DenseMatrixC { return DenseMatrixC(mat); }); + m.def("dense_copy_r", [](const DenseMatrixR &m) -> DenseMatrixR { return m; }); + m.def("dense_copy_c", [](const DenseMatrixC &m) -> DenseMatrixC { return m; }); + // test_sparse, test_sparse_signature + m.def("sparse_r", [mat]() -> SparseMatrixR { + // NOLINTNEXTLINE(clang-analyzer-core.uninitialized.UndefReturn) + return Eigen::SparseView(mat); + }); + m.def("sparse_c", + [mat]() -> SparseMatrixC { return Eigen::SparseView(mat); }); + m.def("sparse_copy_r", [](const SparseMatrixR &m) -> SparseMatrixR { return m; }); + m.def("sparse_copy_c", [](const SparseMatrixC &m) -> SparseMatrixC { return m; }); + // test_partially_fixed + m.def("partial_copy_four_rm_r", [](const FourRowMatrixR &m) -> FourRowMatrixR { return m; }); + m.def("partial_copy_four_rm_c", [](const FourColMatrixR &m) -> FourColMatrixR { return m; }); + m.def("partial_copy_four_cm_r", [](const FourRowMatrixC &m) -> FourRowMatrixC { return m; }); + m.def("partial_copy_four_cm_c", [](const FourColMatrixC &m) -> FourColMatrixC { return m; }); + + // test_cpp_casting + // Test that we can cast a numpy object to a Eigen::MatrixXd explicitly + m.def("cpp_copy", [](py::handle m) { return m.cast()(1, 0); }); + m.def("cpp_ref_c", [](py::handle m) { return m.cast>()(1, 0); }); + m.def("cpp_ref_r", [](py::handle m) { return m.cast>()(1, 0); }); + m.def("cpp_ref_any", + [](py::handle m) { return m.cast>()(1, 0); }); + + // [workaround(intel)] ICC 20/21 breaks with py::arg().stuff, using py::arg{}.stuff works. + + // test_nocopy_wrapper + // Test that we can prevent copying into an argument that would normally copy: First a version + // that would allow copying (if types or strides don't match) for comparison: + m.def("get_elem", &get_elem); + // Now this alternative that calls the tells pybind to fail rather than copy: + m.def( + "get_elem_nocopy", + [](const Eigen::Ref &m) -> double { return get_elem(m); }, + py::arg{}.noconvert()); + // Also test a row-major-only no-copy const ref: + m.def( + "get_elem_rm_nocopy", + [](Eigen::Ref> &m) -> long { + return m(2, 1); + }, + py::arg{}.noconvert()); + + // test_issue738, test_zero_length + // Issue #738: 1×N or N×1 2D matrices were neither accepted nor properly copied with an + // incompatible stride value on the length-1 dimension--but that should be allowed (without + // requiring a copy!) because the stride value can be safely ignored on a size-1 dimension. + // Similarly, 0×N or N×0 matrices were not accepted--again, these should be allowed since + // they contain no data. This particularly affects numpy ≥ 1.23, which sets the strides to + // 0 if any dimension size is 0. + m.def("iss738_f1", + &adjust_matrix &>, + py::arg{}.noconvert()); + m.def("iss738_f2", + &adjust_matrix> &>, + py::arg{}.noconvert()); + + // test_issue1105 + // Issue #1105: when converting from a numpy two-dimensional (Nx1) or (1xN) value into a dense + // eigen Vector or RowVector, the argument would fail to load because the numpy copy would + // fail: numpy won't broadcast a Nx1 into a 1-dimensional vector. + m.def("iss1105_col", [](const Eigen::VectorXd &) { return true; }); + m.def("iss1105_row", [](const Eigen::RowVectorXd &) { return true; }); + + // test_named_arguments + // Make sure named arguments are working properly: + m.def( + "matrix_multiply", + [](const py::EigenDRef &A, + const py::EigenDRef &B) -> Eigen::MatrixXd { + if (A.cols() != B.rows()) { + throw std::domain_error("Nonconformable matrices!"); + } + return A * B; + }, + py::arg("A"), + py::arg("B")); + + // test_custom_operator_new + py::class_(m, "CustomOperatorNew") + .def(py::init<>()) + .def_readonly("a", &CustomOperatorNew::a) + .def_readonly("b", &CustomOperatorNew::b); + + // test_eigen_ref_life_support + // In case of a failure (the caster's temp array does not live long enough), creating + // a new array (np.ones(10)) increases the chances that the temp array will be garbage + // collected and/or that its memory will be overridden with different values. + m.def("get_elem_direct", [](const Eigen::Ref &v) { + py::module_::import("numpy").attr("ones")(10); + return v(5); + }); + m.def("get_elem_indirect", [](std::vector> v) { + py::module_::import("numpy").attr("ones")(10); + return v[0](5); + }); +} diff --git a/pybind11/tests/test_eigen_matrix.py b/pybind11/tests/test_eigen_matrix.py new file mode 100644 index 000000000..b2e76740b --- /dev/null +++ b/pybind11/tests/test_eigen_matrix.py @@ -0,0 +1,807 @@ +import pytest + +from pybind11_tests import ConstructorStats + +np = pytest.importorskip("numpy") +m = pytest.importorskip("pybind11_tests.eigen_matrix") + + +ref = np.array( + [ + [0.0, 3, 0, 0, 0, 11], + [22, 0, 0, 0, 17, 11], + [7, 5, 0, 1, 0, 11], + [0, 0, 0, 0, 0, 11], + [0, 0, 14, 0, 8, 11], + ] +) + + +def assert_equal_ref(mat): + np.testing.assert_array_equal(mat, ref) + + +def assert_sparse_equal_ref(sparse_mat): + assert_equal_ref(sparse_mat.toarray()) + + +def test_fixed(): + assert_equal_ref(m.fixed_c()) + assert_equal_ref(m.fixed_r()) + assert_equal_ref(m.fixed_copy_r(m.fixed_r())) + assert_equal_ref(m.fixed_copy_c(m.fixed_c())) + assert_equal_ref(m.fixed_copy_r(m.fixed_c())) + assert_equal_ref(m.fixed_copy_c(m.fixed_r())) + + +def test_dense(): + assert_equal_ref(m.dense_r()) + assert_equal_ref(m.dense_c()) + assert_equal_ref(m.dense_copy_r(m.dense_r())) + assert_equal_ref(m.dense_copy_c(m.dense_c())) + assert_equal_ref(m.dense_copy_r(m.dense_c())) + assert_equal_ref(m.dense_copy_c(m.dense_r())) + + +def test_partially_fixed(): + ref2 = np.array([[0.0, 1, 2, 3], [4, 5, 6, 7], [8, 9, 10, 11], [12, 13, 14, 15]]) + np.testing.assert_array_equal(m.partial_copy_four_rm_r(ref2), ref2) + np.testing.assert_array_equal(m.partial_copy_four_rm_c(ref2), ref2) + np.testing.assert_array_equal(m.partial_copy_four_rm_r(ref2[:, 1]), ref2[:, [1]]) + np.testing.assert_array_equal(m.partial_copy_four_rm_c(ref2[0, :]), ref2[[0], :]) + np.testing.assert_array_equal( + m.partial_copy_four_rm_r(ref2[:, (0, 2)]), ref2[:, (0, 2)] + ) + np.testing.assert_array_equal( + m.partial_copy_four_rm_c(ref2[(3, 1, 2), :]), ref2[(3, 1, 2), :] + ) + + np.testing.assert_array_equal(m.partial_copy_four_cm_r(ref2), ref2) + np.testing.assert_array_equal(m.partial_copy_four_cm_c(ref2), ref2) + np.testing.assert_array_equal(m.partial_copy_four_cm_r(ref2[:, 1]), ref2[:, [1]]) + np.testing.assert_array_equal(m.partial_copy_four_cm_c(ref2[0, :]), ref2[[0], :]) + np.testing.assert_array_equal( + m.partial_copy_four_cm_r(ref2[:, (0, 2)]), ref2[:, (0, 2)] + ) + np.testing.assert_array_equal( + m.partial_copy_four_cm_c(ref2[(3, 1, 2), :]), ref2[(3, 1, 2), :] + ) + + # TypeError should be raise for a shape mismatch + functions = [ + m.partial_copy_four_rm_r, + m.partial_copy_four_rm_c, + m.partial_copy_four_cm_r, + m.partial_copy_four_cm_c, + ] + matrix_with_wrong_shape = [[1, 2], [3, 4]] + for f in functions: + with pytest.raises(TypeError) as excinfo: + f(matrix_with_wrong_shape) + assert "incompatible function arguments" in str(excinfo.value) + + +def test_mutator_descriptors(): + zr = np.arange(30, dtype="float32").reshape(5, 6) # row-major + zc = zr.reshape(6, 5).transpose() # column-major + + m.fixed_mutator_r(zr) + m.fixed_mutator_c(zc) + m.fixed_mutator_a(zr) + m.fixed_mutator_a(zc) + with pytest.raises(TypeError) as excinfo: + m.fixed_mutator_r(zc) + assert ( + "(arg0: numpy.ndarray[numpy.float32[5, 6]," + " flags.writeable, flags.c_contiguous]) -> None" in str(excinfo.value) + ) + with pytest.raises(TypeError) as excinfo: + m.fixed_mutator_c(zr) + assert ( + "(arg0: numpy.ndarray[numpy.float32[5, 6]," + " flags.writeable, flags.f_contiguous]) -> None" in str(excinfo.value) + ) + with pytest.raises(TypeError) as excinfo: + m.fixed_mutator_a(np.array([[1, 2], [3, 4]], dtype="float32")) + assert "(arg0: numpy.ndarray[numpy.float32[5, 6], flags.writeable]) -> None" in str( + excinfo.value + ) + zr.flags.writeable = False + with pytest.raises(TypeError): + m.fixed_mutator_r(zr) + with pytest.raises(TypeError): + m.fixed_mutator_a(zr) + + +def test_cpp_casting(): + assert m.cpp_copy(m.fixed_r()) == 22.0 + assert m.cpp_copy(m.fixed_c()) == 22.0 + z = np.array([[5.0, 6], [7, 8]]) + assert m.cpp_copy(z) == 7.0 + assert m.cpp_copy(m.get_cm_ref()) == 21.0 + assert m.cpp_copy(m.get_rm_ref()) == 21.0 + assert m.cpp_ref_c(m.get_cm_ref()) == 21.0 + assert m.cpp_ref_r(m.get_rm_ref()) == 21.0 + with pytest.raises(RuntimeError) as excinfo: + # Can't reference m.fixed_c: it contains floats, m.cpp_ref_any wants doubles + m.cpp_ref_any(m.fixed_c()) + assert "Unable to cast Python instance" in str(excinfo.value) + with pytest.raises(RuntimeError) as excinfo: + # Can't reference m.fixed_r: it contains floats, m.cpp_ref_any wants doubles + m.cpp_ref_any(m.fixed_r()) + assert "Unable to cast Python instance" in str(excinfo.value) + assert m.cpp_ref_any(m.ReturnTester.create()) == 1.0 + + assert m.cpp_ref_any(m.get_cm_ref()) == 21.0 + assert m.cpp_ref_any(m.get_cm_ref()) == 21.0 + + +def test_pass_readonly_array(): + z = np.full((5, 6), 42.0) + z.flags.writeable = False + np.testing.assert_array_equal(z, m.fixed_copy_r(z)) + np.testing.assert_array_equal(m.fixed_r_const(), m.fixed_r()) + assert not m.fixed_r_const().flags.writeable + np.testing.assert_array_equal(m.fixed_copy_r(m.fixed_r_const()), m.fixed_r_const()) + + +def test_nonunit_stride_from_python(): + counting_mat = np.arange(9.0, dtype=np.float32).reshape((3, 3)) + second_row = counting_mat[1, :] + second_col = counting_mat[:, 1] + np.testing.assert_array_equal(m.double_row(second_row), 2.0 * second_row) + np.testing.assert_array_equal(m.double_col(second_row), 2.0 * second_row) + np.testing.assert_array_equal(m.double_complex(second_row), 2.0 * second_row) + np.testing.assert_array_equal(m.double_row(second_col), 2.0 * second_col) + np.testing.assert_array_equal(m.double_col(second_col), 2.0 * second_col) + np.testing.assert_array_equal(m.double_complex(second_col), 2.0 * second_col) + + counting_3d = np.arange(27.0, dtype=np.float32).reshape((3, 3, 3)) + slices = [counting_3d[0, :, :], counting_3d[:, 0, :], counting_3d[:, :, 0]] + for ref_mat in slices: + np.testing.assert_array_equal(m.double_mat_cm(ref_mat), 2.0 * ref_mat) + np.testing.assert_array_equal(m.double_mat_rm(ref_mat), 2.0 * ref_mat) + + # Mutator: + m.double_threer(second_row) + m.double_threec(second_col) + np.testing.assert_array_equal(counting_mat, [[0.0, 2, 2], [6, 16, 10], [6, 14, 8]]) + + +def test_negative_stride_from_python(msg): + """Eigen doesn't support (as of yet) negative strides. When a function takes an Eigen matrix by + copy or const reference, we can pass a numpy array that has negative strides. Otherwise, an + exception will be thrown as Eigen will not be able to map the numpy array.""" + + counting_mat = np.arange(9.0, dtype=np.float32).reshape((3, 3)) + counting_mat = counting_mat[::-1, ::-1] + second_row = counting_mat[1, :] + second_col = counting_mat[:, 1] + np.testing.assert_array_equal(m.double_row(second_row), 2.0 * second_row) + np.testing.assert_array_equal(m.double_col(second_row), 2.0 * second_row) + np.testing.assert_array_equal(m.double_complex(second_row), 2.0 * second_row) + np.testing.assert_array_equal(m.double_row(second_col), 2.0 * second_col) + np.testing.assert_array_equal(m.double_col(second_col), 2.0 * second_col) + np.testing.assert_array_equal(m.double_complex(second_col), 2.0 * second_col) + + counting_3d = np.arange(27.0, dtype=np.float32).reshape((3, 3, 3)) + counting_3d = counting_3d[::-1, ::-1, ::-1] + slices = [counting_3d[0, :, :], counting_3d[:, 0, :], counting_3d[:, :, 0]] + for ref_mat in slices: + np.testing.assert_array_equal(m.double_mat_cm(ref_mat), 2.0 * ref_mat) + np.testing.assert_array_equal(m.double_mat_rm(ref_mat), 2.0 * ref_mat) + + # Mutator: + with pytest.raises(TypeError) as excinfo: + m.double_threer(second_row) + assert ( + msg(excinfo.value) + == """ + double_threer(): incompatible function arguments. The following argument types are supported: + 1. (arg0: numpy.ndarray[numpy.float32[1, 3], flags.writeable]) -> None + + Invoked with: """ + + repr(np.array([5.0, 4.0, 3.0], dtype="float32")) + ) + + with pytest.raises(TypeError) as excinfo: + m.double_threec(second_col) + assert ( + msg(excinfo.value) + == """ + double_threec(): incompatible function arguments. The following argument types are supported: + 1. (arg0: numpy.ndarray[numpy.float32[3, 1], flags.writeable]) -> None + + Invoked with: """ + + repr(np.array([7.0, 4.0, 1.0], dtype="float32")) + ) + + +def test_block_runtime_error_type_caster_eigen_ref_made_a_copy(): + with pytest.raises(RuntimeError) as excinfo: + m.block(ref, 0, 0, 0, 0) + assert str(excinfo.value) == "type_caster for Eigen::Ref made a copy." + + +def test_nonunit_stride_to_python(): + assert np.all(m.diagonal(ref) == ref.diagonal()) + assert np.all(m.diagonal_1(ref) == ref.diagonal(1)) + for i in range(-5, 7): + assert np.all(m.diagonal_n(ref, i) == ref.diagonal(i)), f"m.diagonal_n({i})" + + # Must be order="F", otherwise the type_caster will make a copy and + # m.block() will return a dangling reference (heap-use-after-free). + rof = np.asarray(ref, order="F") + assert np.all(m.block(rof, 2, 1, 3, 3) == rof[2:5, 1:4]) + assert np.all(m.block(rof, 1, 4, 4, 2) == rof[1:, 4:]) + assert np.all(m.block(rof, 1, 4, 3, 2) == rof[1:4, 4:]) + + +def test_eigen_ref_to_python(): + chols = [m.cholesky1, m.cholesky2, m.cholesky3, m.cholesky4] + for i, chol in enumerate(chols, start=1): + mymat = chol(np.array([[1.0, 2, 4], [2, 13, 23], [4, 23, 77]])) + assert np.all( + mymat == np.array([[1, 0, 0], [2, 3, 0], [4, 5, 6]]) + ), f"cholesky{i}" + + +def assign_both(a1, a2, r, c, v): + a1[r, c] = v + a2[r, c] = v + + +def array_copy_but_one(a, r, c, v): + z = np.array(a, copy=True) + z[r, c] = v + return z + + +def test_eigen_return_references(): + """Tests various ways of returning references and non-referencing copies""" + + primary = np.ones((10, 10)) + a = m.ReturnTester() + a_get1 = a.get() + assert not a_get1.flags.owndata + assert a_get1.flags.writeable + assign_both(a_get1, primary, 3, 3, 5) + a_get2 = a.get_ptr() + assert not a_get2.flags.owndata + assert a_get2.flags.writeable + assign_both(a_get1, primary, 2, 3, 6) + + a_view1 = a.view() + assert not a_view1.flags.owndata + assert not a_view1.flags.writeable + with pytest.raises(ValueError): + a_view1[2, 3] = 4 + a_view2 = a.view_ptr() + assert not a_view2.flags.owndata + assert not a_view2.flags.writeable + with pytest.raises(ValueError): + a_view2[2, 3] = 4 + + a_copy1 = a.copy_get() + assert a_copy1.flags.owndata + assert a_copy1.flags.writeable + np.testing.assert_array_equal(a_copy1, primary) + a_copy1[7, 7] = -44 # Shouldn't affect anything else + c1want = array_copy_but_one(primary, 7, 7, -44) + a_copy2 = a.copy_view() + assert a_copy2.flags.owndata + assert a_copy2.flags.writeable + np.testing.assert_array_equal(a_copy2, primary) + a_copy2[4, 4] = -22 # Shouldn't affect anything else + c2want = array_copy_but_one(primary, 4, 4, -22) + + a_ref1 = a.ref() + assert not a_ref1.flags.owndata + assert a_ref1.flags.writeable + assign_both(a_ref1, primary, 1, 1, 15) + a_ref2 = a.ref_const() + assert not a_ref2.flags.owndata + assert not a_ref2.flags.writeable + with pytest.raises(ValueError): + a_ref2[5, 5] = 33 + a_ref3 = a.ref_safe() + assert not a_ref3.flags.owndata + assert a_ref3.flags.writeable + assign_both(a_ref3, primary, 0, 7, 99) + a_ref4 = a.ref_const_safe() + assert not a_ref4.flags.owndata + assert not a_ref4.flags.writeable + with pytest.raises(ValueError): + a_ref4[7, 0] = 987654321 + + a_copy3 = a.copy_ref() + assert a_copy3.flags.owndata + assert a_copy3.flags.writeable + np.testing.assert_array_equal(a_copy3, primary) + a_copy3[8, 1] = 11 + c3want = array_copy_but_one(primary, 8, 1, 11) + a_copy4 = a.copy_ref_const() + assert a_copy4.flags.owndata + assert a_copy4.flags.writeable + np.testing.assert_array_equal(a_copy4, primary) + a_copy4[8, 4] = 88 + c4want = array_copy_but_one(primary, 8, 4, 88) + + a_block1 = a.block(3, 3, 2, 2) + assert not a_block1.flags.owndata + assert a_block1.flags.writeable + a_block1[0, 0] = 55 + primary[3, 3] = 55 + a_block2 = a.block_safe(2, 2, 3, 2) + assert not a_block2.flags.owndata + assert a_block2.flags.writeable + a_block2[2, 1] = -123 + primary[4, 3] = -123 + a_block3 = a.block_const(6, 7, 4, 3) + assert not a_block3.flags.owndata + assert not a_block3.flags.writeable + with pytest.raises(ValueError): + a_block3[2, 2] = -44444 + + a_copy5 = a.copy_block(2, 2, 2, 3) + assert a_copy5.flags.owndata + assert a_copy5.flags.writeable + np.testing.assert_array_equal(a_copy5, primary[2:4, 2:5]) + a_copy5[1, 1] = 777 + c5want = array_copy_but_one(primary[2:4, 2:5], 1, 1, 777) + + a_corn1 = a.corners() + assert not a_corn1.flags.owndata + assert a_corn1.flags.writeable + a_corn1 *= 50 + a_corn1[1, 1] = 999 + primary[0, 0] = 50 + primary[0, 9] = 50 + primary[9, 0] = 50 + primary[9, 9] = 999 + a_corn2 = a.corners_const() + assert not a_corn2.flags.owndata + assert not a_corn2.flags.writeable + with pytest.raises(ValueError): + a_corn2[1, 0] = 51 + + # All of the changes made all the way along should be visible everywhere + # now (except for the copies, of course) + np.testing.assert_array_equal(a_get1, primary) + np.testing.assert_array_equal(a_get2, primary) + np.testing.assert_array_equal(a_view1, primary) + np.testing.assert_array_equal(a_view2, primary) + np.testing.assert_array_equal(a_ref1, primary) + np.testing.assert_array_equal(a_ref2, primary) + np.testing.assert_array_equal(a_ref3, primary) + np.testing.assert_array_equal(a_ref4, primary) + np.testing.assert_array_equal(a_block1, primary[3:5, 3:5]) + np.testing.assert_array_equal(a_block2, primary[2:5, 2:4]) + np.testing.assert_array_equal(a_block3, primary[6:10, 7:10]) + np.testing.assert_array_equal( + a_corn1, primary[0 :: primary.shape[0] - 1, 0 :: primary.shape[1] - 1] + ) + np.testing.assert_array_equal( + a_corn2, primary[0 :: primary.shape[0] - 1, 0 :: primary.shape[1] - 1] + ) + + np.testing.assert_array_equal(a_copy1, c1want) + np.testing.assert_array_equal(a_copy2, c2want) + np.testing.assert_array_equal(a_copy3, c3want) + np.testing.assert_array_equal(a_copy4, c4want) + np.testing.assert_array_equal(a_copy5, c5want) + + +def assert_keeps_alive(cl, method, *args): + cstats = ConstructorStats.get(cl) + start_with = cstats.alive() + a = cl() + assert cstats.alive() == start_with + 1 + z = method(a, *args) + assert cstats.alive() == start_with + 1 + del a + # Here's the keep alive in action: + assert cstats.alive() == start_with + 1 + del z + # Keep alive should have expired: + assert cstats.alive() == start_with + + +def test_eigen_keepalive(): + a = m.ReturnTester() + cstats = ConstructorStats.get(m.ReturnTester) + assert cstats.alive() == 1 + unsafe = [a.ref(), a.ref_const(), a.block(1, 2, 3, 4)] + copies = [ + a.copy_get(), + a.copy_view(), + a.copy_ref(), + a.copy_ref_const(), + a.copy_block(4, 3, 2, 1), + ] + del a + assert cstats.alive() == 0 + del unsafe + del copies + + for meth in [ + m.ReturnTester.get, + m.ReturnTester.get_ptr, + m.ReturnTester.view, + m.ReturnTester.view_ptr, + m.ReturnTester.ref_safe, + m.ReturnTester.ref_const_safe, + m.ReturnTester.corners, + m.ReturnTester.corners_const, + ]: + assert_keeps_alive(m.ReturnTester, meth) + + for meth in [m.ReturnTester.block_safe, m.ReturnTester.block_const]: + assert_keeps_alive(m.ReturnTester, meth, 4, 3, 2, 1) + + +def test_eigen_ref_mutators(): + """Tests Eigen's ability to mutate numpy values""" + + orig = np.array([[1.0, 2, 3], [4, 5, 6], [7, 8, 9]]) + zr = np.array(orig) + zc = np.array(orig, order="F") + m.add_rm(zr, 1, 0, 100) + assert np.all(zr == np.array([[1.0, 2, 3], [104, 5, 6], [7, 8, 9]])) + m.add_cm(zc, 1, 0, 200) + assert np.all(zc == np.array([[1.0, 2, 3], [204, 5, 6], [7, 8, 9]])) + + m.add_any(zr, 1, 0, 20) + assert np.all(zr == np.array([[1.0, 2, 3], [124, 5, 6], [7, 8, 9]])) + m.add_any(zc, 1, 0, 10) + assert np.all(zc == np.array([[1.0, 2, 3], [214, 5, 6], [7, 8, 9]])) + + # Can't reference a col-major array with a row-major Ref, and vice versa: + with pytest.raises(TypeError): + m.add_rm(zc, 1, 0, 1) + with pytest.raises(TypeError): + m.add_cm(zr, 1, 0, 1) + + # Overloads: + m.add1(zr, 1, 0, -100) + m.add2(zr, 1, 0, -20) + assert np.all(zr == orig) + m.add1(zc, 1, 0, -200) + m.add2(zc, 1, 0, -10) + assert np.all(zc == orig) + + # a non-contiguous slice (this won't work on either the row- or + # column-contiguous refs, but should work for the any) + cornersr = zr[0::2, 0::2] + cornersc = zc[0::2, 0::2] + + assert np.all(cornersr == np.array([[1.0, 3], [7, 9]])) + assert np.all(cornersc == np.array([[1.0, 3], [7, 9]])) + + with pytest.raises(TypeError): + m.add_rm(cornersr, 0, 1, 25) + with pytest.raises(TypeError): + m.add_cm(cornersr, 0, 1, 25) + with pytest.raises(TypeError): + m.add_rm(cornersc, 0, 1, 25) + with pytest.raises(TypeError): + m.add_cm(cornersc, 0, 1, 25) + m.add_any(cornersr, 0, 1, 25) + m.add_any(cornersc, 0, 1, 44) + assert np.all(zr == np.array([[1.0, 2, 28], [4, 5, 6], [7, 8, 9]])) + assert np.all(zc == np.array([[1.0, 2, 47], [4, 5, 6], [7, 8, 9]])) + + # You shouldn't be allowed to pass a non-writeable array to a mutating Eigen method: + zro = zr[0:4, 0:4] + zro.flags.writeable = False + with pytest.raises(TypeError): + m.add_rm(zro, 0, 0, 0) + with pytest.raises(TypeError): + m.add_any(zro, 0, 0, 0) + with pytest.raises(TypeError): + m.add1(zro, 0, 0, 0) + with pytest.raises(TypeError): + m.add2(zro, 0, 0, 0) + + # integer array shouldn't be passable to a double-matrix-accepting mutating func: + zi = np.array([[1, 2], [3, 4]]) + with pytest.raises(TypeError): + m.add_rm(zi) + + +def test_numpy_ref_mutators(): + """Tests numpy mutating Eigen matrices (for returned Eigen::Ref<...>s)""" + + m.reset_refs() # In case another test already changed it + + zc = m.get_cm_ref() + zcro = m.get_cm_const_ref() + zr = m.get_rm_ref() + zrro = m.get_rm_const_ref() + + assert [zc[1, 2], zcro[1, 2], zr[1, 2], zrro[1, 2]] == [23] * 4 + + assert not zc.flags.owndata + assert zc.flags.writeable + assert not zr.flags.owndata + assert zr.flags.writeable + assert not zcro.flags.owndata + assert not zcro.flags.writeable + assert not zrro.flags.owndata + assert not zrro.flags.writeable + + zc[1, 2] = 99 + expect = np.array([[11.0, 12, 13], [21, 22, 99], [31, 32, 33]]) + # We should have just changed zc, of course, but also zcro and the original eigen matrix + assert np.all(zc == expect) + assert np.all(zcro == expect) + assert np.all(m.get_cm_ref() == expect) + + zr[1, 2] = 99 + assert np.all(zr == expect) + assert np.all(zrro == expect) + assert np.all(m.get_rm_ref() == expect) + + # Make sure the readonly ones are numpy-readonly: + with pytest.raises(ValueError): + zcro[1, 2] = 6 + with pytest.raises(ValueError): + zrro[1, 2] = 6 + + # We should be able to explicitly copy like this (and since we're copying, + # the const should drop away) + y1 = np.array(m.get_cm_const_ref()) + + assert y1.flags.owndata + assert y1.flags.writeable + # We should get copies of the eigen data, which was modified above: + assert y1[1, 2] == 99 + y1[1, 2] += 12 + assert y1[1, 2] == 111 + assert zc[1, 2] == 99 # Make sure we aren't referencing the original + + +def test_both_ref_mutators(): + """Tests a complex chain of nested eigen/numpy references""" + + m.reset_refs() # In case another test already changed it + + z = m.get_cm_ref() # numpy -> eigen + z[0, 2] -= 3 + z2 = m.incr_matrix(z, 1) # numpy -> eigen -> numpy -> eigen + z2[1, 1] += 6 + z3 = m.incr_matrix(z, 2) # (numpy -> eigen)^3 + z3[2, 2] += -5 + z4 = m.incr_matrix(z, 3) # (numpy -> eigen)^4 + z4[1, 1] -= 1 + z5 = m.incr_matrix(z, 4) # (numpy -> eigen)^5 + z5[0, 0] = 0 + assert np.all(z == z2) + assert np.all(z == z3) + assert np.all(z == z4) + assert np.all(z == z5) + expect = np.array([[0.0, 22, 20], [31, 37, 33], [41, 42, 38]]) + assert np.all(z == expect) + + y = np.array(range(100), dtype="float64").reshape(10, 10) + y2 = m.incr_matrix_any(y, 10) # np -> eigen -> np + y3 = m.incr_matrix_any( + y2[0::2, 0::2], -33 + ) # np -> eigen -> np slice -> np -> eigen -> np + y4 = m.even_rows(y3) # numpy -> eigen slice -> (... y3) + y5 = m.even_cols(y4) # numpy -> eigen slice -> (... y4) + y6 = m.incr_matrix_any(y5, 1000) # numpy -> eigen -> (... y5) + + # Apply same mutations using just numpy: + yexpect = np.array(range(100), dtype="float64").reshape(10, 10) + yexpect += 10 + yexpect[0::2, 0::2] -= 33 + yexpect[0::4, 0::4] += 1000 + assert np.all(y6 == yexpect[0::4, 0::4]) + assert np.all(y5 == yexpect[0::4, 0::4]) + assert np.all(y4 == yexpect[0::4, 0::2]) + assert np.all(y3 == yexpect[0::2, 0::2]) + assert np.all(y2 == yexpect) + assert np.all(y == yexpect) + + +def test_nocopy_wrapper(): + # get_elem requires a column-contiguous matrix reference, but should be + # callable with other types of matrix (via copying): + int_matrix_colmajor = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]], order="F") + dbl_matrix_colmajor = np.array( + int_matrix_colmajor, dtype="double", order="F", copy=True + ) + int_matrix_rowmajor = np.array(int_matrix_colmajor, order="C", copy=True) + dbl_matrix_rowmajor = np.array( + int_matrix_rowmajor, dtype="double", order="C", copy=True + ) + + # All should be callable via get_elem: + assert m.get_elem(int_matrix_colmajor) == 8 + assert m.get_elem(dbl_matrix_colmajor) == 8 + assert m.get_elem(int_matrix_rowmajor) == 8 + assert m.get_elem(dbl_matrix_rowmajor) == 8 + + # All but the second should fail with m.get_elem_nocopy: + with pytest.raises(TypeError) as excinfo: + m.get_elem_nocopy(int_matrix_colmajor) + assert "get_elem_nocopy(): incompatible function arguments." in str(excinfo.value) + assert ", flags.f_contiguous" in str(excinfo.value) + assert m.get_elem_nocopy(dbl_matrix_colmajor) == 8 + with pytest.raises(TypeError) as excinfo: + m.get_elem_nocopy(int_matrix_rowmajor) + assert "get_elem_nocopy(): incompatible function arguments." in str(excinfo.value) + assert ", flags.f_contiguous" in str(excinfo.value) + with pytest.raises(TypeError) as excinfo: + m.get_elem_nocopy(dbl_matrix_rowmajor) + assert "get_elem_nocopy(): incompatible function arguments." in str(excinfo.value) + assert ", flags.f_contiguous" in str(excinfo.value) + + # For the row-major test, we take a long matrix in row-major, so only the third is allowed: + with pytest.raises(TypeError) as excinfo: + m.get_elem_rm_nocopy(int_matrix_colmajor) + assert "get_elem_rm_nocopy(): incompatible function arguments." in str( + excinfo.value + ) + assert ", flags.c_contiguous" in str(excinfo.value) + with pytest.raises(TypeError) as excinfo: + m.get_elem_rm_nocopy(dbl_matrix_colmajor) + assert "get_elem_rm_nocopy(): incompatible function arguments." in str( + excinfo.value + ) + assert ", flags.c_contiguous" in str(excinfo.value) + assert m.get_elem_rm_nocopy(int_matrix_rowmajor) == 8 + with pytest.raises(TypeError) as excinfo: + m.get_elem_rm_nocopy(dbl_matrix_rowmajor) + assert "get_elem_rm_nocopy(): incompatible function arguments." in str( + excinfo.value + ) + assert ", flags.c_contiguous" in str(excinfo.value) + + +def test_eigen_ref_life_support(): + """Ensure the lifetime of temporary arrays created by the `Ref` caster + + The `Ref` caster sometimes creates a copy which needs to stay alive. This needs to + happen both for directs casts (just the array) or indirectly (e.g. list of arrays). + """ + + a = np.full(shape=10, fill_value=8, dtype=np.int8) + assert m.get_elem_direct(a) == 8 + + list_of_a = [a] + assert m.get_elem_indirect(list_of_a) == 8 + + +def test_special_matrix_objects(): + assert np.all(m.incr_diag(7) == np.diag([1.0, 2, 3, 4, 5, 6, 7])) + + asymm = np.array([[1.0, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12], [13, 14, 15, 16]]) + symm_lower = np.array(asymm) + symm_upper = np.array(asymm) + for i in range(4): + for j in range(i + 1, 4): + symm_lower[i, j] = symm_lower[j, i] + symm_upper[j, i] = symm_upper[i, j] + + assert np.all(m.symmetric_lower(asymm) == symm_lower) + assert np.all(m.symmetric_upper(asymm) == symm_upper) + + +def test_dense_signature(doc): + assert ( + doc(m.double_col) + == """ + double_col(arg0: numpy.ndarray[numpy.float32[m, 1]]) -> numpy.ndarray[numpy.float32[m, 1]] + """ + ) + assert ( + doc(m.double_row) + == """ + double_row(arg0: numpy.ndarray[numpy.float32[1, n]]) -> numpy.ndarray[numpy.float32[1, n]] + """ + ) + assert doc(m.double_complex) == ( + """ + double_complex(arg0: numpy.ndarray[numpy.complex64[m, 1]])""" + """ -> numpy.ndarray[numpy.complex64[m, 1]] + """ + ) + assert doc(m.double_mat_rm) == ( + """ + double_mat_rm(arg0: numpy.ndarray[numpy.float32[m, n]])""" + """ -> numpy.ndarray[numpy.float32[m, n]] + """ + ) + + +def test_named_arguments(): + a = np.array([[1.0, 2], [3, 4], [5, 6]]) + b = np.ones((2, 1)) + + assert np.all(m.matrix_multiply(a, b) == np.array([[3.0], [7], [11]])) + assert np.all(m.matrix_multiply(A=a, B=b) == np.array([[3.0], [7], [11]])) + assert np.all(m.matrix_multiply(B=b, A=a) == np.array([[3.0], [7], [11]])) + + with pytest.raises(ValueError) as excinfo: + m.matrix_multiply(b, a) + assert str(excinfo.value) == "Nonconformable matrices!" + + with pytest.raises(ValueError) as excinfo: + m.matrix_multiply(A=b, B=a) + assert str(excinfo.value) == "Nonconformable matrices!" + + with pytest.raises(ValueError) as excinfo: + m.matrix_multiply(B=a, A=b) + assert str(excinfo.value) == "Nonconformable matrices!" + + +def test_sparse(): + pytest.importorskip("scipy") + assert_sparse_equal_ref(m.sparse_r()) + assert_sparse_equal_ref(m.sparse_c()) + assert_sparse_equal_ref(m.sparse_copy_r(m.sparse_r())) + assert_sparse_equal_ref(m.sparse_copy_c(m.sparse_c())) + assert_sparse_equal_ref(m.sparse_copy_r(m.sparse_c())) + assert_sparse_equal_ref(m.sparse_copy_c(m.sparse_r())) + + +def test_sparse_signature(doc): + pytest.importorskip("scipy") + assert ( + doc(m.sparse_copy_r) + == """ + sparse_copy_r(arg0: scipy.sparse.csr_matrix[numpy.float32]) -> scipy.sparse.csr_matrix[numpy.float32] + """ + ) + assert ( + doc(m.sparse_copy_c) + == """ + sparse_copy_c(arg0: scipy.sparse.csc_matrix[numpy.float32]) -> scipy.sparse.csc_matrix[numpy.float32] + """ + ) + + +def test_issue738(): + """Ignore strides on a length-1 dimension (even if they would be incompatible length > 1)""" + assert np.all(m.iss738_f1(np.array([[1.0, 2, 3]])) == np.array([[1.0, 102, 203]])) + assert np.all( + m.iss738_f1(np.array([[1.0], [2], [3]])) == np.array([[1.0], [12], [23]]) + ) + + assert np.all(m.iss738_f2(np.array([[1.0, 2, 3]])) == np.array([[1.0, 102, 203]])) + assert np.all( + m.iss738_f2(np.array([[1.0], [2], [3]])) == np.array([[1.0], [12], [23]]) + ) + + +@pytest.mark.parametrize("func", [m.iss738_f1, m.iss738_f2]) +@pytest.mark.parametrize("sizes", [(0, 2), (2, 0)]) +def test_zero_length(func, sizes): + """Ignore strides on a length-0 dimension (even if they would be incompatible length > 1)""" + assert np.all(func(np.zeros(sizes)) == np.zeros(sizes)) + + +def test_issue1105(): + """Issue 1105: 1xN or Nx1 input arrays weren't accepted for eigen + compile-time row vectors or column vector""" + assert m.iss1105_row(np.ones((1, 7))) + assert m.iss1105_col(np.ones((7, 1))) + + # These should still fail (incompatible dimensions): + with pytest.raises(TypeError) as excinfo: + m.iss1105_row(np.ones((7, 1))) + assert "incompatible function arguments" in str(excinfo.value) + with pytest.raises(TypeError) as excinfo: + m.iss1105_col(np.ones((1, 7))) + assert "incompatible function arguments" in str(excinfo.value) + + +def test_custom_operator_new(): + """Using Eigen types as member variables requires a class-specific + operator new with proper alignment""" + + o = m.CustomOperatorNew() + np.testing.assert_allclose(o.a, 0.0) + np.testing.assert_allclose(o.b.diagonal(), 1.0) diff --git a/pybind11/tests/test_eigen_tensor.cpp b/pybind11/tests/test_eigen_tensor.cpp new file mode 100644 index 000000000..503c69c7d --- /dev/null +++ b/pybind11/tests/test_eigen_tensor.cpp @@ -0,0 +1,18 @@ +/* + tests/eigen_tensor.cpp -- automatic conversion of Eigen Tensor + + All rights reserved. Use of this source code is governed by a + BSD-style license that can be found in the LICENSE file. +*/ + +#define PYBIND11_TEST_EIGEN_TENSOR_NAMESPACE eigen_tensor + +#ifdef EIGEN_AVOID_STL_ARRAY +# undef EIGEN_AVOID_STL_ARRAY +#endif + +#include "test_eigen_tensor.inl" + +#include "pybind11_tests.h" + +test_initializer egien_tensor("eigen_tensor", eigen_tensor_test::test_module); diff --git a/pybind11/tests/test_eigen_tensor.inl b/pybind11/tests/test_eigen_tensor.inl new file mode 100644 index 000000000..d864ce737 --- /dev/null +++ b/pybind11/tests/test_eigen_tensor.inl @@ -0,0 +1,333 @@ +/* + tests/eigen_tensor.cpp -- automatic conversion of Eigen Tensor + + All rights reserved. Use of this source code is governed by a + BSD-style license that can be found in the LICENSE file. +*/ + +#include + +PYBIND11_NAMESPACE_BEGIN(eigen_tensor_test) + +namespace py = pybind11; + +PYBIND11_WARNING_DISABLE_MSVC(4127) + +template +void reset_tensor(M &x) { + for (int i = 0; i < x.dimension(0); i++) { + for (int j = 0; j < x.dimension(1); j++) { + for (int k = 0; k < x.dimension(2); k++) { + x(i, j, k) = i * (5 * 2) + j * 2 + k; + } + } + } +} + +template +bool check_tensor(M &x) { + for (int i = 0; i < x.dimension(0); i++) { + for (int j = 0; j < x.dimension(1); j++) { + for (int k = 0; k < x.dimension(2); k++) { + if (x(i, j, k) != (i * (5 * 2) + j * 2 + k)) { + return false; + } + } + } + } + return true; +} + +template +Eigen::Tensor &get_tensor() { + static Eigen::Tensor *x; + + if (!x) { + x = new Eigen::Tensor(3, 5, 2); + reset_tensor(*x); + } + + return *x; +} + +template +Eigen::TensorMap> &get_tensor_map() { + static Eigen::TensorMap> *x; + + if (!x) { + x = new Eigen::TensorMap>(get_tensor()); + } + + return *x; +} + +template +Eigen::TensorFixedSize, Options> &get_fixed_tensor() { + static Eigen::TensorFixedSize, Options> *x; + + if (!x) { + Eigen::aligned_allocator, Options>> + allocator; + x = new (allocator.allocate(1)) + Eigen::TensorFixedSize, Options>(); + reset_tensor(*x); + } + + return *x; +} + +template +const Eigen::Tensor &get_const_tensor() { + return get_tensor(); +} + +template +struct CustomExample { + CustomExample() : member(get_tensor()), view_member(member) {} + + Eigen::Tensor member; + Eigen::TensorMap> view_member; +}; + +template +void init_tensor_module(pybind11::module &m) { + const char *needed_options = ""; + if (Options == Eigen::ColMajor) { + needed_options = "F"; + } else { + needed_options = "C"; + } + m.attr("needed_options") = needed_options; + + m.def("setup", []() { + reset_tensor(get_tensor()); + reset_tensor(get_fixed_tensor()); + }); + + m.def("is_ok", []() { + return check_tensor(get_tensor()) && check_tensor(get_fixed_tensor()); + }); + + py::class_>(m, "CustomExample", py::module_local()) + .def(py::init<>()) + .def_readonly( + "member", &CustomExample::member, py::return_value_policy::reference_internal) + .def_readonly("member_view", + &CustomExample::view_member, + py::return_value_policy::reference_internal); + + m.def( + "copy_fixed_tensor", + []() { return &get_fixed_tensor(); }, + py::return_value_policy::copy); + + m.def( + "copy_tensor", []() { return &get_tensor(); }, py::return_value_policy::copy); + + m.def( + "copy_const_tensor", + []() { return &get_const_tensor(); }, + py::return_value_policy::copy); + + m.def( + "move_fixed_tensor_copy", + []() -> Eigen::TensorFixedSize, Options> { + return get_fixed_tensor(); + }, + py::return_value_policy::move); + + m.def( + "move_tensor_copy", + []() -> Eigen::Tensor { return get_tensor(); }, + py::return_value_policy::move); + + m.def( + "move_const_tensor", + []() -> const Eigen::Tensor & { return get_const_tensor(); }, + py::return_value_policy::move); + + m.def( + "take_fixed_tensor", + + []() { + Eigen::aligned_allocator< + Eigen::TensorFixedSize, Options>> + allocator; + return new (allocator.allocate(1)) + Eigen::TensorFixedSize, Options>( + get_fixed_tensor()); + }, + py::return_value_policy::take_ownership); + + m.def( + "take_tensor", + []() { return new Eigen::Tensor(get_tensor()); }, + py::return_value_policy::take_ownership); + + m.def( + "take_const_tensor", + []() -> const Eigen::Tensor * { + return new Eigen::Tensor(get_tensor()); + }, + py::return_value_policy::take_ownership); + + m.def( + "take_view_tensor", + []() -> const Eigen::TensorMap> * { + return new Eigen::TensorMap>(get_tensor()); + }, + py::return_value_policy::take_ownership); + + m.def( + "reference_tensor", + []() { return &get_tensor(); }, + py::return_value_policy::reference); + + m.def( + "reference_tensor_v2", + []() -> Eigen::Tensor & { return get_tensor(); }, + py::return_value_policy::reference); + + m.def( + "reference_tensor_internal", + []() { return &get_tensor(); }, + py::return_value_policy::reference_internal); + + m.def( + "reference_fixed_tensor", + []() { return &get_tensor(); }, + py::return_value_policy::reference); + + m.def( + "reference_const_tensor", + []() { return &get_const_tensor(); }, + py::return_value_policy::reference); + + m.def( + "reference_const_tensor_v2", + []() -> const Eigen::Tensor & { return get_const_tensor(); }, + py::return_value_policy::reference); + + m.def( + "reference_view_of_tensor", + []() -> Eigen::TensorMap> { + return get_tensor_map(); + }, + py::return_value_policy::reference); + + m.def( + "reference_view_of_tensor_v2", + // NOLINTNEXTLINE(readability-const-return-type) + []() -> const Eigen::TensorMap> { + return get_tensor_map(); // NOLINT(readability-const-return-type) + }, // NOLINT(readability-const-return-type) + py::return_value_policy::reference); + + m.def( + "reference_view_of_tensor_v3", + []() -> Eigen::TensorMap> * { + return &get_tensor_map(); + }, + py::return_value_policy::reference); + + m.def( + "reference_view_of_tensor_v4", + []() -> const Eigen::TensorMap> * { + return &get_tensor_map(); + }, + py::return_value_policy::reference); + + m.def( + "reference_view_of_tensor_v5", + []() -> Eigen::TensorMap> & { + return get_tensor_map(); + }, + py::return_value_policy::reference); + + m.def( + "reference_view_of_tensor_v6", + []() -> const Eigen::TensorMap> & { + return get_tensor_map(); + }, + py::return_value_policy::reference); + + m.def( + "reference_view_of_fixed_tensor", + []() { + return Eigen::TensorMap< + Eigen::TensorFixedSize, Options>>( + get_fixed_tensor()); + }, + py::return_value_policy::reference); + + m.def("round_trip_tensor", + [](const Eigen::Tensor &tensor) { return tensor; }); + + m.def( + "round_trip_tensor_noconvert", + [](const Eigen::Tensor &tensor) { return tensor; }, + py::arg("tensor").noconvert()); + + m.def("round_trip_tensor2", + [](const Eigen::Tensor &tensor) { return tensor; }); + + m.def("round_trip_fixed_tensor", + [](const Eigen::TensorFixedSize, Options> &tensor) { + return tensor; + }); + + m.def( + "round_trip_view_tensor", + [](Eigen::TensorMap> view) { return view; }, + py::return_value_policy::reference); + + m.def( + "round_trip_view_tensor_ref", + [](Eigen::TensorMap> &view) { return view; }, + py::return_value_policy::reference); + + m.def( + "round_trip_view_tensor_ptr", + [](Eigen::TensorMap> *view) { return view; }, + py::return_value_policy::reference); + + m.def( + "round_trip_aligned_view_tensor", + [](Eigen::TensorMap, Eigen::Aligned> view) { + return view; + }, + py::return_value_policy::reference); + + m.def( + "round_trip_const_view_tensor", + [](Eigen::TensorMap> view) { + return Eigen::Tensor(view); + }, + py::return_value_policy::move); + + m.def( + "round_trip_rank_0", + [](const Eigen::Tensor &tensor) { return tensor; }, + py::return_value_policy::move); + + m.def( + "round_trip_rank_0_noconvert", + [](const Eigen::Tensor &tensor) { return tensor; }, + py::arg("tensor").noconvert(), + py::return_value_policy::move); + + m.def( + "round_trip_rank_0_view", + [](Eigen::TensorMap> &tensor) { return tensor; }, + py::return_value_policy::reference); +} + +void test_module(py::module_ &m) { + auto f_style = m.def_submodule("f_style"); + auto c_style = m.def_submodule("c_style"); + + init_tensor_module(f_style); + init_tensor_module(c_style); +} + +PYBIND11_NAMESPACE_END(eigen_tensor_test) diff --git a/pybind11/tests/test_eigen_tensor.py b/pybind11/tests/test_eigen_tensor.py new file mode 100644 index 000000000..3e7ee6b7f --- /dev/null +++ b/pybind11/tests/test_eigen_tensor.py @@ -0,0 +1,288 @@ +import sys + +import pytest + +np = pytest.importorskip("numpy") +eigen_tensor = pytest.importorskip("pybind11_tests.eigen_tensor") +submodules = [eigen_tensor.c_style, eigen_tensor.f_style] +try: + import eigen_tensor_avoid_stl_array as avoid + + submodules += [avoid.c_style, avoid.f_style] +except ImportError as e: + # Ensure config, build, toolchain, etc. issues are not masked here: + msg = ( + "import eigen_tensor_avoid_stl_array FAILED, while " + "import pybind11_tests.eigen_tensor succeeded. " + "Please ensure that " + "test_eigen_tensor.cpp & " + "eigen_tensor_avoid_stl_array.cpp " + "are built together (or both are not built if Eigen is not available)." + ) + raise RuntimeError(msg) from e + +tensor_ref = np.empty((3, 5, 2), dtype=np.int64) + +for i in range(tensor_ref.shape[0]): + for j in range(tensor_ref.shape[1]): + for k in range(tensor_ref.shape[2]): + tensor_ref[i, j, k] = i * (5 * 2) + j * 2 + k + +indices = (2, 3, 1) + + +@pytest.fixture(autouse=True) +def cleanup(): + for module in submodules: + module.setup() + + yield + + for module in submodules: + assert module.is_ok() + + +def test_import_avoid_stl_array(): + pytest.importorskip("eigen_tensor_avoid_stl_array") + assert len(submodules) == 4 + + +def assert_equal_tensor_ref(mat, writeable=True, modified=None): + assert mat.flags.writeable == writeable + + copy = np.array(tensor_ref) + if modified is not None: + copy[indices] = modified + + np.testing.assert_array_equal(mat, copy) + + +@pytest.mark.parametrize("m", submodules) +@pytest.mark.parametrize("member_name", ["member", "member_view"]) +def test_reference_internal(m, member_name): + if not hasattr(sys, "getrefcount"): + pytest.skip("No reference counting") + foo = m.CustomExample() + counts = sys.getrefcount(foo) + mem = getattr(foo, member_name) + assert_equal_tensor_ref(mem, writeable=False) + new_counts = sys.getrefcount(foo) + assert new_counts == counts + 1 + assert_equal_tensor_ref(mem, writeable=False) + del mem + assert sys.getrefcount(foo) == counts + + +assert_equal_funcs = [ + "copy_tensor", + "copy_fixed_tensor", + "copy_const_tensor", + "move_tensor_copy", + "move_fixed_tensor_copy", + "take_tensor", + "take_fixed_tensor", + "reference_tensor", + "reference_tensor_v2", + "reference_fixed_tensor", + "reference_view_of_tensor", + "reference_view_of_tensor_v3", + "reference_view_of_tensor_v5", + "reference_view_of_fixed_tensor", +] + +assert_equal_const_funcs = [ + "reference_view_of_tensor_v2", + "reference_view_of_tensor_v4", + "reference_view_of_tensor_v6", + "reference_const_tensor", + "reference_const_tensor_v2", +] + + +@pytest.mark.parametrize("m", submodules) +@pytest.mark.parametrize("func_name", assert_equal_funcs + assert_equal_const_funcs) +def test_convert_tensor_to_py(m, func_name): + writeable = func_name in assert_equal_funcs + assert_equal_tensor_ref(getattr(m, func_name)(), writeable=writeable) + + +@pytest.mark.parametrize("m", submodules) +def test_bad_cpp_to_python_casts(m): + with pytest.raises( + RuntimeError, match="Cannot use reference internal when there is no parent" + ): + m.reference_tensor_internal() + + with pytest.raises(RuntimeError, match="Cannot move from a constant reference"): + m.move_const_tensor() + + with pytest.raises( + RuntimeError, match="Cannot take ownership of a const reference" + ): + m.take_const_tensor() + + with pytest.raises( + RuntimeError, + match="Invalid return_value_policy for Eigen Map type, must be either reference or reference_internal", + ): + m.take_view_tensor() + + +@pytest.mark.parametrize("m", submodules) +def test_bad_python_to_cpp_casts(m): + with pytest.raises( + TypeError, match=r"^round_trip_tensor\(\): incompatible function arguments" + ): + m.round_trip_tensor(np.zeros((2, 3))) + + with pytest.raises(TypeError, match=r"^Cannot cast array data from dtype"): + m.round_trip_tensor(np.zeros(dtype=np.str_, shape=(2, 3, 1))) + + with pytest.raises( + TypeError, + match=r"^round_trip_tensor_noconvert\(\): incompatible function arguments", + ): + m.round_trip_tensor_noconvert(tensor_ref) + + assert_equal_tensor_ref( + m.round_trip_tensor_noconvert(tensor_ref.astype(np.float64)) + ) + + bad_options = "C" if m.needed_options == "F" else "F" + # Shape, dtype and the order need to be correct for a TensorMap cast + with pytest.raises( + TypeError, match=r"^round_trip_view_tensor\(\): incompatible function arguments" + ): + m.round_trip_view_tensor( + np.zeros((3, 5, 2), dtype=np.float64, order=bad_options) + ) + + with pytest.raises( + TypeError, match=r"^round_trip_view_tensor\(\): incompatible function arguments" + ): + m.round_trip_view_tensor( + np.zeros((3, 5, 2), dtype=np.float32, order=m.needed_options) + ) + + with pytest.raises( + TypeError, match=r"^round_trip_view_tensor\(\): incompatible function arguments" + ): + m.round_trip_view_tensor( + np.zeros((3, 5), dtype=np.float64, order=m.needed_options) + ) + + temp = np.zeros((3, 5, 2), dtype=np.float64, order=m.needed_options) + with pytest.raises( + TypeError, match=r"^round_trip_view_tensor\(\): incompatible function arguments" + ): + m.round_trip_view_tensor( + temp[:, ::-1, :], + ) + + temp = np.zeros((3, 5, 2), dtype=np.float64, order=m.needed_options) + temp.setflags(write=False) + with pytest.raises( + TypeError, match=r"^round_trip_view_tensor\(\): incompatible function arguments" + ): + m.round_trip_view_tensor(temp) + + +@pytest.mark.parametrize("m", submodules) +def test_references_actually_refer(m): + a = m.reference_tensor() + temp = a[indices] + a[indices] = 100 + assert_equal_tensor_ref(m.copy_const_tensor(), modified=100) + a[indices] = temp + assert_equal_tensor_ref(m.copy_const_tensor()) + + a = m.reference_view_of_tensor() + a[indices] = 100 + assert_equal_tensor_ref(m.copy_const_tensor(), modified=100) + a[indices] = temp + assert_equal_tensor_ref(m.copy_const_tensor()) + + +@pytest.mark.parametrize("m", submodules) +def test_round_trip(m): + assert_equal_tensor_ref(m.round_trip_tensor(tensor_ref)) + + with pytest.raises(TypeError, match="^Cannot cast array data from"): + assert_equal_tensor_ref(m.round_trip_tensor2(tensor_ref)) + + assert_equal_tensor_ref(m.round_trip_tensor2(np.array(tensor_ref, dtype=np.int32))) + assert_equal_tensor_ref(m.round_trip_fixed_tensor(tensor_ref)) + assert_equal_tensor_ref(m.round_trip_aligned_view_tensor(m.reference_tensor())) + + copy = np.array(tensor_ref, dtype=np.float64, order=m.needed_options) + assert_equal_tensor_ref(m.round_trip_view_tensor(copy)) + assert_equal_tensor_ref(m.round_trip_view_tensor_ref(copy)) + assert_equal_tensor_ref(m.round_trip_view_tensor_ptr(copy)) + copy.setflags(write=False) + assert_equal_tensor_ref(m.round_trip_const_view_tensor(copy)) + + np.testing.assert_array_equal( + tensor_ref[:, ::-1, :], m.round_trip_tensor(tensor_ref[:, ::-1, :]) + ) + + assert m.round_trip_rank_0(np.float64(3.5)) == 3.5 + assert m.round_trip_rank_0(3.5) == 3.5 + + with pytest.raises( + TypeError, + match=r"^round_trip_rank_0_noconvert\(\): incompatible function arguments", + ): + m.round_trip_rank_0_noconvert(np.float64(3.5)) + + with pytest.raises( + TypeError, + match=r"^round_trip_rank_0_noconvert\(\): incompatible function arguments", + ): + m.round_trip_rank_0_noconvert(3.5) + + with pytest.raises( + TypeError, match=r"^round_trip_rank_0_view\(\): incompatible function arguments" + ): + m.round_trip_rank_0_view(np.float64(3.5)) + + with pytest.raises( + TypeError, match=r"^round_trip_rank_0_view\(\): incompatible function arguments" + ): + m.round_trip_rank_0_view(3.5) + + +@pytest.mark.parametrize("m", submodules) +def test_round_trip_references_actually_refer(m): + # Need to create a copy that matches the type on the C side + copy = np.array(tensor_ref, dtype=np.float64, order=m.needed_options) + a = m.round_trip_view_tensor(copy) + temp = a[indices] + a[indices] = 100 + assert_equal_tensor_ref(copy, modified=100) + a[indices] = temp + assert_equal_tensor_ref(copy) + + +@pytest.mark.parametrize("m", submodules) +def test_doc_string(m, doc): + assert ( + doc(m.copy_tensor) == "copy_tensor() -> numpy.ndarray[numpy.float64[?, ?, ?]]" + ) + assert ( + doc(m.copy_fixed_tensor) + == "copy_fixed_tensor() -> numpy.ndarray[numpy.float64[3, 5, 2]]" + ) + assert ( + doc(m.reference_const_tensor) + == "reference_const_tensor() -> numpy.ndarray[numpy.float64[?, ?, ?]]" + ) + + order_flag = f"flags.{m.needed_options.lower()}_contiguous" + assert doc(m.round_trip_view_tensor) == ( + f"round_trip_view_tensor(arg0: numpy.ndarray[numpy.float64[?, ?, ?], flags.writeable, {order_flag}])" + f" -> numpy.ndarray[numpy.float64[?, ?, ?], flags.writeable, {order_flag}]" + ) + assert doc(m.round_trip_const_view_tensor) == ( + f"round_trip_const_view_tensor(arg0: numpy.ndarray[numpy.float64[?, ?, ?], {order_flag}])" + " -> numpy.ndarray[numpy.float64[?, ?, ?]]" + ) diff --git a/pybind11/tests/test_embed/catch.cpp b/pybind11/tests/test_embed/catch.cpp index 96d2e3f92..558a7a35e 100644 --- a/pybind11/tests/test_embed/catch.cpp +++ b/pybind11/tests/test_embed/catch.cpp @@ -3,11 +3,9 @@ #include -#ifdef _MSC_VER // Silence MSVC C++17 deprecation warning from Catch regarding std::uncaught_exceptions (up to // catch 2.0.1; this should be fixed in the next catch release after 2.0.1). -# pragma warning(disable : 4996) -#endif +PYBIND11_WARNING_DISABLE_MSVC(4996) // Catch uses _ internally, which breaks gettext style defines #ifdef _ @@ -20,7 +18,25 @@ namespace py = pybind11; int main(int argc, char *argv[]) { + // Setup for TEST_CASE in test_interpreter.cpp, tagging on a large random number: + std::string updated_pythonpath("pybind11_test_embed_PYTHONPATH_2099743835476552"); + const char *preexisting_pythonpath = getenv("PYTHONPATH"); + if (preexisting_pythonpath != nullptr) { +#if defined(_WIN32) + updated_pythonpath += ';'; +#else + updated_pythonpath += ':'; +#endif + updated_pythonpath += preexisting_pythonpath; + } +#if defined(_WIN32) + _putenv_s("PYTHONPATH", updated_pythonpath.c_str()); +#else + setenv("PYTHONPATH", updated_pythonpath.c_str(), /*replace=*/1); +#endif + py::scoped_interpreter guard{}; + auto result = Catch::Session().run(argc, argv); return result < 0xff ? result : 0xff; diff --git a/pybind11/tests/test_embed/test_interpreter.cpp b/pybind11/tests/test_embed/test_interpreter.cpp index 1c45457a0..c6c8a22d9 100644 --- a/pybind11/tests/test_embed/test_interpreter.cpp +++ b/pybind11/tests/test_embed/test_interpreter.cpp @@ -1,10 +1,8 @@ #include -#ifdef _MSC_VER // Silence MSVC C++17 deprecation warning from Catch regarding std::uncaught_exceptions (up to // catch 2.0.1; this should be fixed in the next catch release after 2.0.1). -# pragma warning(disable : 4996) -#endif +PYBIND11_WARNING_DISABLE_MSVC(4996) #include #include @@ -16,6 +14,11 @@ namespace py = pybind11; using namespace py::literals; +size_t get_sys_path_size() { + auto sys_path = py::module::import("sys").attr("path"); + return py::len(sys_path); +} + class Widget { public: explicit Widget(std::string message) : message(std::move(message)) {} @@ -75,6 +78,13 @@ PYBIND11_EMBEDDED_MODULE(throw_error_already_set, ) { d["missing"].cast(); } +TEST_CASE("PYTHONPATH is used to update sys.path") { + // The setup for this TEST_CASE is in catch.cpp! + auto sys_path = py::str(py::module_::import("sys").attr("path")).cast(); + REQUIRE_THAT(sys_path, + Catch::Matchers::Contains("pybind11_test_embed_PYTHONPATH_2099743835476552")); +} + TEST_CASE("Pass classes and data between modules defined in C++ and Python") { auto module_ = py::module_::import("test_interpreter"); REQUIRE(py::hasattr(module_, "DerivedWidget")); @@ -161,10 +171,94 @@ TEST_CASE("There can be only one interpreter") { py::initialize_interpreter(); } -bool has_pybind11_internals_builtin() { - auto builtins = py::handle(PyEval_GetBuiltins()); - return builtins.contains(PYBIND11_INTERNALS_ID); -}; +#if PY_VERSION_HEX >= PYBIND11_PYCONFIG_SUPPORT_PY_VERSION_HEX +TEST_CASE("Custom PyConfig") { + py::finalize_interpreter(); + PyConfig config; + PyConfig_InitPythonConfig(&config); + REQUIRE_NOTHROW(py::scoped_interpreter{&config}); + { + py::scoped_interpreter p{&config}; + REQUIRE(py::module_::import("widget_module").attr("add")(1, 41).cast() == 42); + } + py::initialize_interpreter(); +} + +TEST_CASE("scoped_interpreter with PyConfig_InitIsolatedConfig and argv") { + py::finalize_interpreter(); + { + PyConfig config; + PyConfig_InitIsolatedConfig(&config); + char *argv[] = {strdup("a.out")}; + py::scoped_interpreter argv_scope{&config, 1, argv}; + std::free(argv[0]); + auto module = py::module::import("test_interpreter"); + auto py_widget = module.attr("DerivedWidget")("The question"); + const auto &cpp_widget = py_widget.cast(); + REQUIRE(cpp_widget.argv0() == "a.out"); + } + py::initialize_interpreter(); +} + +TEST_CASE("scoped_interpreter with PyConfig_InitPythonConfig and argv") { + py::finalize_interpreter(); + { + PyConfig config; + PyConfig_InitPythonConfig(&config); + + // `initialize_interpreter() overrides the default value for config.parse_argv (`1`) by + // changing it to `0`. This test exercises `scoped_interpreter` with the default config. + char *argv[] = {strdup("a.out"), strdup("arg1")}; + py::scoped_interpreter argv_scope(&config, 2, argv); + std::free(argv[0]); + std::free(argv[1]); + auto module = py::module::import("test_interpreter"); + auto py_widget = module.attr("DerivedWidget")("The question"); + const auto &cpp_widget = py_widget.cast(); + REQUIRE(cpp_widget.argv0() == "arg1"); + } + py::initialize_interpreter(); +} +#endif + +TEST_CASE("Add program dir to path pre-PyConfig") { + py::finalize_interpreter(); + size_t path_size_add_program_dir_to_path_false = 0; + { + py::scoped_interpreter scoped_interp{true, 0, nullptr, false}; + path_size_add_program_dir_to_path_false = get_sys_path_size(); + } + { + py::scoped_interpreter scoped_interp{}; + REQUIRE(get_sys_path_size() == path_size_add_program_dir_to_path_false + 1); + } + py::initialize_interpreter(); +} + +#if PY_VERSION_HEX >= PYBIND11_PYCONFIG_SUPPORT_PY_VERSION_HEX +TEST_CASE("Add program dir to path using PyConfig") { + py::finalize_interpreter(); + size_t path_size_add_program_dir_to_path_false = 0; + { + PyConfig config; + PyConfig_InitPythonConfig(&config); + py::scoped_interpreter scoped_interp{&config, 0, nullptr, false}; + path_size_add_program_dir_to_path_false = get_sys_path_size(); + } + { + PyConfig config; + PyConfig_InitPythonConfig(&config); + py::scoped_interpreter scoped_interp{&config}; + REQUIRE(get_sys_path_size() == path_size_add_program_dir_to_path_false + 1); + } + py::initialize_interpreter(); +} +#endif + +bool has_state_dict_internals_obj() { + return bool( + py::detail::get_internals_obj_from_state_dict(py::detail::get_python_state_dict())); +} bool has_pybind11_internals_static() { auto **&ipp = py::detail::get_internals_pp(); @@ -174,7 +268,7 @@ bool has_pybind11_internals_static() { TEST_CASE("Restart the interpreter") { // Verify pre-restart state. REQUIRE(py::module_::import("widget_module").attr("add")(1, 2).cast() == 3); - REQUIRE(has_pybind11_internals_builtin()); + REQUIRE(has_state_dict_internals_obj()); REQUIRE(has_pybind11_internals_static()); REQUIRE(py::module_::import("external_module").attr("A")(123).attr("value").cast() == 123); @@ -191,10 +285,10 @@ TEST_CASE("Restart the interpreter") { REQUIRE(Py_IsInitialized() == 1); // Internals are deleted after a restart. - REQUIRE_FALSE(has_pybind11_internals_builtin()); + REQUIRE_FALSE(has_state_dict_internals_obj()); REQUIRE_FALSE(has_pybind11_internals_static()); pybind11::detail::get_internals(); - REQUIRE(has_pybind11_internals_builtin()); + REQUIRE(has_state_dict_internals_obj()); REQUIRE(has_pybind11_internals_static()); REQUIRE(reinterpret_cast(*py::detail::get_internals_pp()) == py::module_::import("external_module").attr("internals_at")().cast()); @@ -209,13 +303,13 @@ TEST_CASE("Restart the interpreter") { py::detail::get_internals(); *static_cast(ran) = true; }); - REQUIRE_FALSE(has_pybind11_internals_builtin()); + REQUIRE_FALSE(has_state_dict_internals_obj()); REQUIRE_FALSE(has_pybind11_internals_static()); REQUIRE_FALSE(ran); py::finalize_interpreter(); REQUIRE(ran); py::initialize_interpreter(); - REQUIRE_FALSE(has_pybind11_internals_builtin()); + REQUIRE_FALSE(has_state_dict_internals_obj()); REQUIRE_FALSE(has_pybind11_internals_static()); // C++ modules can be reloaded. @@ -237,7 +331,7 @@ TEST_CASE("Subinterpreter") { REQUIRE(m.attr("add")(1, 2).cast() == 3); } - REQUIRE(has_pybind11_internals_builtin()); + REQUIRE(has_state_dict_internals_obj()); REQUIRE(has_pybind11_internals_static()); /// Create and switch to a subinterpreter. @@ -247,7 +341,7 @@ TEST_CASE("Subinterpreter") { // Subinterpreters get their own copy of builtins. detail::get_internals() still // works by returning from the static variable, i.e. all interpreters share a single // global pybind11::internals; - REQUIRE_FALSE(has_pybind11_internals_builtin()); + REQUIRE_FALSE(has_state_dict_internals_obj()); REQUIRE(has_pybind11_internals_static()); // Modules tags should be gone. @@ -286,7 +380,6 @@ TEST_CASE("Threads") { { py::gil_scoped_release gil_release{}; - REQUIRE(has_pybind11_internals_static()); auto threads = std::vector(); for (auto i = 0; i < num_threads; ++i) { diff --git a/pybind11/tests/test_enum.py b/pybind11/tests/test_enum.py index f14a72398..4e85d29c3 100644 --- a/pybind11/tests/test_enum.py +++ b/pybind11/tests/test_enum.py @@ -1,3 +1,5 @@ +# ruff: noqa: SIM201 SIM300 SIM202 + import pytest from pybind11_tests import enums as m diff --git a/pybind11/tests/test_exceptions.cpp b/pybind11/tests/test_exceptions.cpp index 3ec999d1d..854c7e6f7 100644 --- a/pybind11/tests/test_exceptions.cpp +++ b/pybind11/tests/test_exceptions.cpp @@ -105,11 +105,6 @@ struct PythonAlreadySetInDestructor { py::str s; }; -std::string error_already_set_what(const py::object &exc_type, const py::object &exc_value) { - PyErr_SetObject(exc_type.ptr(), exc_value.ptr()); - return py::error_already_set().what(); -} - TEST_SUBMODULE(exceptions, m) { m.def("throw_std_exception", []() { throw std::runtime_error("This exception was intentionally thrown."); }); @@ -334,4 +329,19 @@ TEST_SUBMODULE(exceptions, m) { e.restore(); } }); + + // https://github.com/pybind/pybind11/issues/4075 + m.def("test_pypy_oserror_normalization", []() { + try { + py::module_::import("io").attr("open")("this_filename_must_not_exist", "r"); + } catch (const py::error_already_set &e) { + return py::str(e.what()); // str must be built before e goes out of scope. + } + return py::str("UNEXPECTED"); + }); + + m.def("test_fn_cast_int", [](const py::function &fn) { + // function returns None instead of int, should give a useful error message + fn().cast(); + }); } diff --git a/pybind11/tests/test_exceptions.py b/pybind11/tests/test_exceptions.py index a5984a142..ccac4536d 100644 --- a/pybind11/tests/test_exceptions.py +++ b/pybind11/tests/test_exceptions.py @@ -4,6 +4,7 @@ import pytest import env import pybind11_cross_module_tests as cm +import pybind11_tests # noqa: F401 from pybind11_tests import exceptions as m @@ -72,9 +73,9 @@ def test_cross_module_exceptions(msg): # TODO: FIXME @pytest.mark.xfail( - "env.PYPY and env.MACOS", + "env.MACOS and (env.PYPY or pybind11_tests.compiler_info.startswith('Homebrew Clang'))", raises=RuntimeError, - reason="Expected failure with PyPy and libc++ (Issue #2847 & PR #2999)", + reason="See Issue #2847, PR #2999, PR #4324", ) def test_cross_module_exception_translator(): with pytest.raises(KeyError): @@ -93,8 +94,7 @@ def ignore_pytest_unraisable_warning(f): if hasattr(pytest, unraisable): # Python >= 3.8 and pytest >= 6 dec = pytest.mark.filterwarnings(f"ignore::pytest.{unraisable}") return dec(f) - else: - return f + return f # TODO: find out why this fails on PyPy, https://foss.heptapod.net/pypy/pypy/-/issues/3583 @@ -182,11 +182,11 @@ def test_custom(msg): m.throws5_1() assert msg(excinfo.value) == "MyException5 subclass" - with pytest.raises(m.MyException5) as excinfo: + with pytest.raises(m.MyException5) as excinfo: # noqa: PT012 try: m.throws5() - except m.MyException5_1: - raise RuntimeError("Exception error: caught child from parent") + except m.MyException5_1 as err: + raise RuntimeError("Exception error: caught child from parent") from err assert msg(excinfo.value) == "this is a helper-defined translated exception" @@ -211,7 +211,7 @@ def test_nested_throws(capture): m.try_catch(m.MyException5, throw_myex) assert str(excinfo.value) == "nested error" - def pycatch(exctype, f, *args): + def pycatch(exctype, f, *args): # noqa: ARG001 try: f(*args) except m.MyException as e: @@ -275,6 +275,20 @@ def test_local_translator(msg): assert msg(excinfo.value) == "this mod" +def test_error_already_set_message_with_unicode_surrogate(): # Issue #4288 + assert m.error_already_set_what(RuntimeError, "\ud927") == ( + "RuntimeError: \\ud927", + False, + ) + + +def test_error_already_set_message_with_malformed_utf8(): + assert m.error_already_set_what(RuntimeError, b"\x80") == ( + "RuntimeError: b'\\x80'", + False, + ) + + class FlakyException(Exception): def __init__(self, failure_point): if failure_point == "failure_point_init": @@ -288,12 +302,12 @@ class FlakyException(Exception): @pytest.mark.parametrize( - "exc_type, exc_value, expected_what", - ( + ("exc_type", "exc_value", "expected_what"), + [ (ValueError, "plain_str", "ValueError: plain_str"), (ValueError, ("tuple_elem",), "ValueError: tuple_elem"), (FlakyException, ("happy",), "FlakyException: FlakyException.__str__"), - ), + ], ) def test_error_already_set_what_with_happy_exceptions( exc_type, exc_value, expected_what @@ -303,8 +317,7 @@ def test_error_already_set_what_with_happy_exceptions( assert what == expected_what -@pytest.mark.skipif("env.PYPY", reason="PyErr_NormalizeException Segmentation fault") -def test_flaky_exception_failure_point_init(): +def _test_flaky_exception_failure_point_init_before_py_3_12(): with pytest.raises(RuntimeError) as excinfo: m.error_already_set_what(FlakyException, ("failure_point_init",)) lines = str(excinfo.value).splitlines() @@ -318,7 +331,33 @@ def test_flaky_exception_failure_point_init(): # Checking the first two lines of the traceback as formatted in error_string(): assert "test_exceptions.py(" in lines[3] assert lines[3].endswith("): __init__") - assert lines[4].endswith("): test_flaky_exception_failure_point_init") + assert lines[4].endswith( + "): _test_flaky_exception_failure_point_init_before_py_3_12" + ) + + +def _test_flaky_exception_failure_point_init_py_3_12(): + # Behavior change in Python 3.12: https://github.com/python/cpython/issues/102594 + what, py_err_set_after_what = m.error_already_set_what( + FlakyException, ("failure_point_init",) + ) + assert not py_err_set_after_what + lines = what.splitlines() + assert lines[0].endswith("ValueError[WITH __notes__]: triggered_failure_point_init") + assert lines[1] == "__notes__ (len=1):" + assert "Normalization failed:" in lines[2] + assert "FlakyException" in lines[2] + + +@pytest.mark.skipif( + "env.PYPY and sys.version_info[:2] < (3, 12)", + reason="PyErr_NormalizeException Segmentation fault", +) +def test_flaky_exception_failure_point_init(): + if sys.version_info[:2] < (3, 12): + _test_flaky_exception_failure_point_init_before_py_3_12() + else: + _test_flaky_exception_failure_point_init_py_3_12() def test_flaky_exception_failure_point_str(): @@ -327,10 +366,7 @@ def test_flaky_exception_failure_point_str(): ) assert not py_err_set_after_what lines = what.splitlines() - if env.PYPY and len(lines) == 3: - n = 3 # Traceback is missing. - else: - n = 5 + n = 3 if env.PYPY and len(lines) == 3 else 5 assert ( lines[:n] == [ @@ -360,3 +396,18 @@ def test_error_already_set_double_restore(): "Internal error: pybind11::detail::error_fetch_and_normalize::restore()" " called a second time. ORIGINAL ERROR: ValueError: Random error." ) + + +def test_pypy_oserror_normalization(): + # https://github.com/pybind/pybind11/issues/4075 + what = m.test_pypy_oserror_normalization() + assert "this_filename_must_not_exist" in what + + +def test_fn_cast_int_exception(): + with pytest.raises(RuntimeError) as excinfo: + m.test_fn_cast_int(lambda: None) + + assert str(excinfo.value).startswith( + "Unable to cast Python instance of type to C++ type" + ) diff --git a/pybind11/tests/test_factory_constructors.py b/pybind11/tests/test_factory_constructors.py index 120a587c4..04df80260 100644 --- a/pybind11/tests/test_factory_constructors.py +++ b/pybind11/tests/test_factory_constructors.py @@ -96,7 +96,7 @@ def test_init_factory_signature(msg): 3. __init__(self: m.factory_constructors.TestFactory1, arg0: m.factory_constructors.tag.pointer_tag) -> None 4. __init__(self: m.factory_constructors.TestFactory1, arg0: handle, arg1: int, arg2: handle) -> None - """ # noqa: E501 line too long + """ ) diff --git a/pybind11/tests/test_gil_scoped.cpp b/pybind11/tests/test_gil_scoped.cpp index 97efdc161..f136086e8 100644 --- a/pybind11/tests/test_gil_scoped.cpp +++ b/pybind11/tests/test_gil_scoped.cpp @@ -11,6 +11,13 @@ #include "pybind11_tests.h" +#include +#include + +#define CROSS_MODULE(Function) \ + auto cm = py::module_::import("cross_module_gil_utils"); \ + auto target = reinterpret_cast(PyLong_AsVoidPtr(cm.attr(Function).ptr())); + class VirtClass { public: virtual ~VirtClass() = default; @@ -28,6 +35,16 @@ class PyVirtClass : public VirtClass { }; TEST_SUBMODULE(gil_scoped, m) { + m.attr("defined_THREAD_SANITIZER") = +#if defined(THREAD_SANITIZER) + true; +#else + false; +#endif + + m.def("intentional_deadlock", + []() { std::thread([]() { py::gil_scoped_acquire gil_acquired; }).join(); }); + py::class_(m, "VirtClass") .def(py::init<>()) .def("virtual_func", &VirtClass::virtual_func) @@ -37,11 +54,91 @@ TEST_SUBMODULE(gil_scoped, m) { m.def("test_callback_std_func", [](const std::function &func) { func(); }); m.def("test_callback_virtual_func", [](VirtClass &virt) { virt.virtual_func(); }); m.def("test_callback_pure_virtual_func", [](VirtClass &virt) { virt.pure_virtual_func(); }); - m.def("test_cross_module_gil", []() { - auto cm = py::module_::import("cross_module_gil_utils"); - auto gil_acquire = reinterpret_cast( - PyLong_AsVoidPtr(cm.attr("gil_acquire_funcaddr").ptr())); + m.def("test_cross_module_gil_released", []() { + CROSS_MODULE("gil_acquire_funcaddr") py::gil_scoped_release gil_release; - gil_acquire(); + target(); + }); + m.def("test_cross_module_gil_acquired", []() { + CROSS_MODULE("gil_acquire_funcaddr") + py::gil_scoped_acquire gil_acquire; + target(); + }); + m.def("test_cross_module_gil_inner_custom_released", []() { + CROSS_MODULE("gil_acquire_inner_custom_funcaddr") + py::gil_scoped_release gil_release; + target(); + }); + m.def("test_cross_module_gil_inner_custom_acquired", []() { + CROSS_MODULE("gil_acquire_inner_custom_funcaddr") + py::gil_scoped_acquire gil_acquire; + target(); + }); + m.def("test_cross_module_gil_inner_pybind11_released", []() { + CROSS_MODULE("gil_acquire_inner_pybind11_funcaddr") + py::gil_scoped_release gil_release; + target(); + }); + m.def("test_cross_module_gil_inner_pybind11_acquired", []() { + CROSS_MODULE("gil_acquire_inner_pybind11_funcaddr") + py::gil_scoped_acquire gil_acquire; + target(); + }); + m.def("test_cross_module_gil_nested_custom_released", []() { + CROSS_MODULE("gil_acquire_nested_custom_funcaddr") + py::gil_scoped_release gil_release; + target(); + }); + m.def("test_cross_module_gil_nested_custom_acquired", []() { + CROSS_MODULE("gil_acquire_nested_custom_funcaddr") + py::gil_scoped_acquire gil_acquire; + target(); + }); + m.def("test_cross_module_gil_nested_pybind11_released", []() { + CROSS_MODULE("gil_acquire_nested_pybind11_funcaddr") + py::gil_scoped_release gil_release; + target(); + }); + m.def("test_cross_module_gil_nested_pybind11_acquired", []() { + CROSS_MODULE("gil_acquire_nested_pybind11_funcaddr") + py::gil_scoped_acquire gil_acquire; + target(); + }); + m.def("test_release_acquire", [](const py::object &obj) { + py::gil_scoped_release gil_released; + py::gil_scoped_acquire gil_acquired; + return py::str(obj); + }); + m.def("test_nested_acquire", [](const py::object &obj) { + py::gil_scoped_release gil_released; + py::gil_scoped_acquire gil_acquired_outer; + py::gil_scoped_acquire gil_acquired_inner; + return py::str(obj); + }); + m.def("test_multi_acquire_release_cross_module", [](unsigned bits) { + py::set internals_ids; + internals_ids.add(PYBIND11_INTERNALS_ID); + { + py::gil_scoped_release gil_released; + auto thread_f = [bits, &internals_ids]() { + py::gil_scoped_acquire gil_acquired; + auto cm = py::module_::import("cross_module_gil_utils"); + auto target = reinterpret_cast( + PyLong_AsVoidPtr(cm.attr("gil_multi_acquire_release_funcaddr").ptr())); + std::string cm_internals_id = target(bits >> 3); + internals_ids.add(cm_internals_id); + }; + if ((bits & 0x1u) != 0u) { + thread_f(); + } + if ((bits & 0x2u) != 0u) { + std::thread non_python_thread(thread_f); + non_python_thread.join(); + } + if ((bits & 0x4u) != 0u) { + thread_f(); + } + } + return internals_ids; }); } diff --git a/pybind11/tests/test_gil_scoped.py b/pybind11/tests/test_gil_scoped.py index 52374b0cc..fc8af9b77 100644 --- a/pybind11/tests/test_gil_scoped.py +++ b/pybind11/tests/test_gil_scoped.py @@ -1,45 +1,197 @@ import multiprocessing +import sys import threading +import time +import pytest + +import env from pybind11_tests import gil_scoped as m +class ExtendedVirtClass(m.VirtClass): + def virtual_func(self): + pass + + def pure_virtual_func(self): + pass + + +def test_callback_py_obj(): + m.test_callback_py_obj(lambda: None) + + +def test_callback_std_func(): + m.test_callback_std_func(lambda: None) + + +def test_callback_virtual_func(): + extended = ExtendedVirtClass() + m.test_callback_virtual_func(extended) + + +def test_callback_pure_virtual_func(): + extended = ExtendedVirtClass() + m.test_callback_pure_virtual_func(extended) + + +def test_cross_module_gil_released(): + """Makes sure that the GIL can be acquired by another module from a GIL-released state.""" + m.test_cross_module_gil_released() # Should not raise a SIGSEGV + + +def test_cross_module_gil_acquired(): + """Makes sure that the GIL can be acquired by another module from a GIL-acquired state.""" + m.test_cross_module_gil_acquired() # Should not raise a SIGSEGV + + +def test_cross_module_gil_inner_custom_released(): + """Makes sure that the GIL can be acquired/released by another module + from a GIL-released state using custom locking logic.""" + m.test_cross_module_gil_inner_custom_released() + + +def test_cross_module_gil_inner_custom_acquired(): + """Makes sure that the GIL can be acquired/acquired by another module + from a GIL-acquired state using custom locking logic.""" + m.test_cross_module_gil_inner_custom_acquired() + + +def test_cross_module_gil_inner_pybind11_released(): + """Makes sure that the GIL can be acquired/released by another module + from a GIL-released state using pybind11 locking logic.""" + m.test_cross_module_gil_inner_pybind11_released() + + +def test_cross_module_gil_inner_pybind11_acquired(): + """Makes sure that the GIL can be acquired/acquired by another module + from a GIL-acquired state using pybind11 locking logic.""" + m.test_cross_module_gil_inner_pybind11_acquired() + + +def test_cross_module_gil_nested_custom_released(): + """Makes sure that the GIL can be nested acquired/released by another module + from a GIL-released state using custom locking logic.""" + m.test_cross_module_gil_nested_custom_released() + + +def test_cross_module_gil_nested_custom_acquired(): + """Makes sure that the GIL can be nested acquired/acquired by another module + from a GIL-acquired state using custom locking logic.""" + m.test_cross_module_gil_nested_custom_acquired() + + +def test_cross_module_gil_nested_pybind11_released(): + """Makes sure that the GIL can be nested acquired/released by another module + from a GIL-released state using pybind11 locking logic.""" + m.test_cross_module_gil_nested_pybind11_released() + + +def test_cross_module_gil_nested_pybind11_acquired(): + """Makes sure that the GIL can be nested acquired/acquired by another module + from a GIL-acquired state using pybind11 locking logic.""" + m.test_cross_module_gil_nested_pybind11_acquired() + + +def test_release_acquire(): + assert m.test_release_acquire(0xAB) == "171" + + +def test_nested_acquire(): + assert m.test_nested_acquire(0xAB) == "171" + + +def test_multi_acquire_release_cross_module(): + for bits in range(16 * 8): + internals_ids = m.test_multi_acquire_release_cross_module(bits) + assert len(internals_ids) == 2 if bits % 8 else 1 + + +# Intentionally putting human review in the loop here, to guard against accidents. +VARS_BEFORE_ALL_BASIC_TESTS = dict(vars()) # Make a copy of the dict (critical). +ALL_BASIC_TESTS = ( + test_callback_py_obj, + test_callback_std_func, + test_callback_virtual_func, + test_callback_pure_virtual_func, + test_cross_module_gil_released, + test_cross_module_gil_acquired, + test_cross_module_gil_inner_custom_released, + test_cross_module_gil_inner_custom_acquired, + test_cross_module_gil_inner_pybind11_released, + test_cross_module_gil_inner_pybind11_acquired, + test_cross_module_gil_nested_custom_released, + test_cross_module_gil_nested_custom_acquired, + test_cross_module_gil_nested_pybind11_released, + test_cross_module_gil_nested_pybind11_acquired, + test_release_acquire, + test_nested_acquire, + test_multi_acquire_release_cross_module, +) + + +def test_all_basic_tests_completeness(): + num_found = 0 + for key, value in VARS_BEFORE_ALL_BASIC_TESTS.items(): + if not key.startswith("test_"): + continue + assert value in ALL_BASIC_TESTS + num_found += 1 + assert len(ALL_BASIC_TESTS) == num_found + + +def _intentional_deadlock(): + m.intentional_deadlock() + + +ALL_BASIC_TESTS_PLUS_INTENTIONAL_DEADLOCK = ALL_BASIC_TESTS + (_intentional_deadlock,) + + def _run_in_process(target, *args, **kwargs): - """Runs target in process and returns its exitcode after 10s (None if still alive).""" + test_fn = target if len(args) == 0 else args[0] + # Do not need to wait much, 10s should be more than enough. + timeout = 0.1 if test_fn is _intentional_deadlock else 10 process = multiprocessing.Process(target=target, args=args, kwargs=kwargs) process.daemon = True try: + t_start = time.time() process.start() - # Do not need to wait much, 10s should be more than enough. - process.join(timeout=10) + if timeout >= 100: # For debugging. + print( + "\nprocess.pid STARTED", process.pid, (sys.argv, target, args, kwargs) + ) + print(f"COPY-PASTE-THIS: gdb {sys.argv[0]} -p {process.pid}", flush=True) + process.join(timeout=timeout) + if timeout >= 100: + print("\nprocess.pid JOINED", process.pid, flush=True) + t_delta = time.time() - t_start + if process.exitcode == 66 and m.defined_THREAD_SANITIZER: # Issue #2754 + # WOULD-BE-NICE-TO-HAVE: Check that the message below is actually in the output. + # Maybe this could work: + # https://gist.github.com/alexeygrigorev/01ce847f2e721b513b42ea4a6c96905e + pytest.skip( + "ThreadSanitizer: starting new threads after multi-threaded fork is not supported." + ) + elif test_fn is _intentional_deadlock: + assert process.exitcode is None + return 0 + + if process.exitcode is None: + assert t_delta > 0.9 * timeout + msg = "DEADLOCK, most likely, exactly what this test is meant to detect." + if env.PYPY and env.WIN: + pytest.skip(msg) + raise RuntimeError(msg) return process.exitcode finally: if process.is_alive(): process.terminate() -def _python_to_cpp_to_python(): - """Calls different C++ functions that come back to Python.""" - - class ExtendedVirtClass(m.VirtClass): - def virtual_func(self): - pass - - def pure_virtual_func(self): - pass - - extended = ExtendedVirtClass() - m.test_callback_py_obj(lambda: None) - m.test_callback_std_func(lambda: None) - m.test_callback_virtual_func(extended) - m.test_callback_pure_virtual_func(extended) - - -def _python_to_cpp_to_python_from_threads(num_threads, parallel=False): - """Calls different C++ functions that come back to Python, from Python threads.""" +def _run_in_threads(test_fn, num_threads, parallel): threads = [] for _ in range(num_threads): - thread = threading.Thread(target=_python_to_cpp_to_python) + thread = threading.Thread(target=test_fn) thread.daemon = True thread.start() if parallel: @@ -51,43 +203,40 @@ def _python_to_cpp_to_python_from_threads(num_threads, parallel=False): # TODO: FIXME, sometimes returns -11 (segfault) instead of 0 on macOS Python 3.9 -def test_python_to_cpp_to_python_from_thread(): +@pytest.mark.parametrize("test_fn", ALL_BASIC_TESTS_PLUS_INTENTIONAL_DEADLOCK) +def test_run_in_process_one_thread(test_fn): """Makes sure there is no GIL deadlock when running in a thread. It runs in a separate process to be able to stop and assert if it deadlocks. """ - assert _run_in_process(_python_to_cpp_to_python_from_threads, 1) == 0 + assert _run_in_process(_run_in_threads, test_fn, num_threads=1, parallel=False) == 0 # TODO: FIXME on macOS Python 3.9 -def test_python_to_cpp_to_python_from_thread_multiple_parallel(): +@pytest.mark.parametrize("test_fn", ALL_BASIC_TESTS_PLUS_INTENTIONAL_DEADLOCK) +def test_run_in_process_multiple_threads_parallel(test_fn): """Makes sure there is no GIL deadlock when running in a thread multiple times in parallel. It runs in a separate process to be able to stop and assert if it deadlocks. """ - assert _run_in_process(_python_to_cpp_to_python_from_threads, 8, parallel=True) == 0 + assert _run_in_process(_run_in_threads, test_fn, num_threads=8, parallel=True) == 0 # TODO: FIXME on macOS Python 3.9 -def test_python_to_cpp_to_python_from_thread_multiple_sequential(): +@pytest.mark.parametrize("test_fn", ALL_BASIC_TESTS_PLUS_INTENTIONAL_DEADLOCK) +def test_run_in_process_multiple_threads_sequential(test_fn): """Makes sure there is no GIL deadlock when running in a thread multiple times sequentially. It runs in a separate process to be able to stop and assert if it deadlocks. """ - assert ( - _run_in_process(_python_to_cpp_to_python_from_threads, 8, parallel=False) == 0 - ) + assert _run_in_process(_run_in_threads, test_fn, num_threads=8, parallel=False) == 0 # TODO: FIXME on macOS Python 3.9 -def test_python_to_cpp_to_python_from_process(): +@pytest.mark.parametrize("test_fn", ALL_BASIC_TESTS_PLUS_INTENTIONAL_DEADLOCK) +def test_run_in_process_direct(test_fn): """Makes sure there is no GIL deadlock when using processes. This test is for completion, but it was never an issue. """ - assert _run_in_process(_python_to_cpp_to_python) == 0 - - -def test_cross_module_gil(): - """Makes sure that the GIL can be acquired by another module from a GIL-released state.""" - m.test_cross_module_gil() # Should not raise a SIGSEGV + assert _run_in_process(test_fn) == 0 diff --git a/pybind11/tests/test_iostream.py b/pybind11/tests/test_iostream.py index 5bbdf6955..d283eb152 100644 --- a/pybind11/tests/test_iostream.py +++ b/pybind11/tests/test_iostream.py @@ -9,16 +9,16 @@ def test_captured(capsys): m.captured_output(msg) stdout, stderr = capsys.readouterr() assert stdout == msg - assert stderr == "" + assert not stderr m.captured_output_default(msg) stdout, stderr = capsys.readouterr() assert stdout == msg - assert stderr == "" + assert not stderr m.captured_err(msg) stdout, stderr = capsys.readouterr() - assert stdout == "" + assert not stdout assert stderr == msg @@ -30,7 +30,7 @@ def test_captured_large_string(capsys): m.captured_output_default(msg) stdout, stderr = capsys.readouterr() assert stdout == msg - assert stderr == "" + assert not stderr def test_captured_utf8_2byte_offset0(capsys): @@ -40,7 +40,7 @@ def test_captured_utf8_2byte_offset0(capsys): m.captured_output_default(msg) stdout, stderr = capsys.readouterr() assert stdout == msg - assert stderr == "" + assert not stderr def test_captured_utf8_2byte_offset1(capsys): @@ -50,7 +50,7 @@ def test_captured_utf8_2byte_offset1(capsys): m.captured_output_default(msg) stdout, stderr = capsys.readouterr() assert stdout == msg - assert stderr == "" + assert not stderr def test_captured_utf8_3byte_offset0(capsys): @@ -60,7 +60,7 @@ def test_captured_utf8_3byte_offset0(capsys): m.captured_output_default(msg) stdout, stderr = capsys.readouterr() assert stdout == msg - assert stderr == "" + assert not stderr def test_captured_utf8_3byte_offset1(capsys): @@ -70,7 +70,7 @@ def test_captured_utf8_3byte_offset1(capsys): m.captured_output_default(msg) stdout, stderr = capsys.readouterr() assert stdout == msg - assert stderr == "" + assert not stderr def test_captured_utf8_3byte_offset2(capsys): @@ -80,7 +80,7 @@ def test_captured_utf8_3byte_offset2(capsys): m.captured_output_default(msg) stdout, stderr = capsys.readouterr() assert stdout == msg - assert stderr == "" + assert not stderr def test_captured_utf8_4byte_offset0(capsys): @@ -90,7 +90,7 @@ def test_captured_utf8_4byte_offset0(capsys): m.captured_output_default(msg) stdout, stderr = capsys.readouterr() assert stdout == msg - assert stderr == "" + assert not stderr def test_captured_utf8_4byte_offset1(capsys): @@ -100,7 +100,7 @@ def test_captured_utf8_4byte_offset1(capsys): m.captured_output_default(msg) stdout, stderr = capsys.readouterr() assert stdout == msg - assert stderr == "" + assert not stderr def test_captured_utf8_4byte_offset2(capsys): @@ -110,7 +110,7 @@ def test_captured_utf8_4byte_offset2(capsys): m.captured_output_default(msg) stdout, stderr = capsys.readouterr() assert stdout == msg - assert stderr == "" + assert not stderr def test_captured_utf8_4byte_offset3(capsys): @@ -120,7 +120,7 @@ def test_captured_utf8_4byte_offset3(capsys): m.captured_output_default(msg) stdout, stderr = capsys.readouterr() assert stdout == msg - assert stderr == "" + assert not stderr def test_guard_capture(capsys): @@ -128,7 +128,7 @@ def test_guard_capture(capsys): m.guard_output(msg) stdout, stderr = capsys.readouterr() assert stdout == msg - assert stderr == "" + assert not stderr def test_series_captured(capture): @@ -145,7 +145,7 @@ def test_flush(capfd): with m.ostream_redirect(): m.noisy_function(msg, flush=False) stdout, stderr = capfd.readouterr() - assert stdout == "" + assert not stdout m.noisy_function(msg2, flush=True) stdout, stderr = capfd.readouterr() @@ -164,15 +164,15 @@ def test_not_captured(capfd): m.raw_output(msg) stdout, stderr = capfd.readouterr() assert stdout == msg - assert stderr == "" - assert stream.getvalue() == "" + assert not stderr + assert not stream.getvalue() stream = StringIO() with redirect_stdout(stream): m.captured_output(msg) stdout, stderr = capfd.readouterr() - assert stdout == "" - assert stderr == "" + assert not stdout + assert not stderr assert stream.getvalue() == msg @@ -182,16 +182,16 @@ def test_err(capfd): with redirect_stderr(stream): m.raw_err(msg) stdout, stderr = capfd.readouterr() - assert stdout == "" + assert not stdout assert stderr == msg - assert stream.getvalue() == "" + assert not stream.getvalue() stream = StringIO() with redirect_stderr(stream): m.captured_err(msg) stdout, stderr = capfd.readouterr() - assert stdout == "" - assert stderr == "" + assert not stdout + assert not stderr assert stream.getvalue() == msg @@ -221,14 +221,13 @@ def test_redirect(capfd): m.raw_output(msg) stdout, stderr = capfd.readouterr() assert stdout == msg - assert stream.getvalue() == "" + assert not stream.getvalue() stream = StringIO() - with redirect_stdout(stream): - with m.ostream_redirect(): - m.raw_output(msg) + with redirect_stdout(stream), m.ostream_redirect(): + m.raw_output(msg) stdout, stderr = capfd.readouterr() - assert stdout == "" + assert not stdout assert stream.getvalue() == msg stream = StringIO() @@ -236,7 +235,7 @@ def test_redirect(capfd): m.raw_output(msg) stdout, stderr = capfd.readouterr() assert stdout == msg - assert stream.getvalue() == "" + assert not stream.getvalue() def test_redirect_err(capfd): @@ -244,13 +243,12 @@ def test_redirect_err(capfd): msg2 = "StdErr" stream = StringIO() - with redirect_stderr(stream): - with m.ostream_redirect(stdout=False): - m.raw_output(msg) - m.raw_err(msg2) + with redirect_stderr(stream), m.ostream_redirect(stdout=False): + m.raw_output(msg) + m.raw_err(msg2) stdout, stderr = capfd.readouterr() assert stdout == msg - assert stderr == "" + assert not stderr assert stream.getvalue() == msg2 @@ -260,14 +258,12 @@ def test_redirect_both(capfd): stream = StringIO() stream2 = StringIO() - with redirect_stdout(stream): - with redirect_stderr(stream2): - with m.ostream_redirect(): - m.raw_output(msg) - m.raw_err(msg2) + with redirect_stdout(stream), redirect_stderr(stream2), m.ostream_redirect(): + m.raw_output(msg) + m.raw_err(msg2) stdout, stderr = capfd.readouterr() - assert stdout == "" - assert stderr == "" + assert not stdout + assert not stderr assert stream.getvalue() == msg assert stream2.getvalue() == msg2 diff --git a/pybind11/tests/test_kwargs_and_defaults.cpp b/pybind11/tests/test_kwargs_and_defaults.cpp index 7418afefb..77e72c0c7 100644 --- a/pybind11/tests/test_kwargs_and_defaults.cpp +++ b/pybind11/tests/test_kwargs_and_defaults.cpp @@ -43,7 +43,15 @@ TEST_SUBMODULE(kwargs_and_defaults, m) { m.def("kw_func_udl_z", kw_func, "x"_a, "y"_a = 0); // test_args_and_kwargs - m.def("args_function", [](py::args args) -> py::tuple { return std::move(args); }); + m.def("args_function", [](py::args args) -> py::tuple { + PYBIND11_WARNING_PUSH + +#ifdef PYBIND11_DETECTED_CLANG_WITH_MISLEADING_CALL_STD_MOVE_EXPLICITLY_WARNING + PYBIND11_WARNING_DISABLE_CLANG("-Wreturn-std-move") +#endif + return args; + PYBIND11_WARNING_POP + }); m.def("args_kwargs_function", [](const py::args &args, const py::kwargs &kwargs) { return py::make_tuple(args, kwargs); }); diff --git a/pybind11/tests/test_kwargs_and_defaults.py b/pybind11/tests/test_kwargs_and_defaults.py index ab7017886..7174726fc 100644 --- a/pybind11/tests/test_kwargs_and_defaults.py +++ b/pybind11/tests/test_kwargs_and_defaults.py @@ -25,7 +25,7 @@ def test_function_signatures(doc): ) -def test_named_arguments(msg): +def test_named_arguments(): assert m.kw_func0(5, 10) == "x=5, y=10" assert m.kw_func1(5, 10) == "x=5, y=10" @@ -43,8 +43,7 @@ def test_named_arguments(msg): # noinspection PyArgumentList m.kw_func2(x=5, y=10, z=12) assert excinfo.match( - r"(?s)^kw_func2\(\): incompatible.*Invoked with: kwargs: ((x=5|y=10|z=12)(, |$))" - + "{3}$" + r"(?s)^kw_func2\(\): incompatible.*Invoked with: kwargs: ((x=5|y=10|z=12)(, |$)){3}$" ) assert m.kw_func4() == "{13 17}" @@ -59,7 +58,7 @@ def test_arg_and_kwargs(): assert m.args_function(*args) == args args = "a1", "a2" - kwargs = dict(arg3="a3", arg4=4) + kwargs = {"arg3": "a3", "arg4": 4} assert m.args_kwargs_function(*args, **kwargs) == (args, kwargs) @@ -177,7 +176,7 @@ def test_mixed_args_and_kwargs(msg): assert ( m.args_kwonly_kwargs_defaults.__doc__ - == "args_kwonly_kwargs_defaults(i: int = 1, j: float = 3.14159, *args, z: int = 42, **kwargs) -> tuple\n" # noqa: E501 line too long + == "args_kwonly_kwargs_defaults(i: int = 1, j: float = 3.14159, *args, z: int = 42, **kwargs) -> tuple\n" ) assert m.args_kwonly_kwargs_defaults() == (1, 3.14159, (), 42, {}) assert m.args_kwonly_kwargs_defaults(2) == (2, 3.14159, (), 42, {}) @@ -233,15 +232,15 @@ def test_keyword_only_args(msg): x.method(i=1, j=2) assert ( m.first_arg_kw_only.__init__.__doc__ - == "__init__(self: pybind11_tests.kwargs_and_defaults.first_arg_kw_only, *, i: int = 0) -> None\n" # noqa: E501 line too long + == "__init__(self: pybind11_tests.kwargs_and_defaults.first_arg_kw_only, *, i: int = 0) -> None\n" ) assert ( m.first_arg_kw_only.method.__doc__ - == "method(self: pybind11_tests.kwargs_and_defaults.first_arg_kw_only, *, i: int = 1, j: int = 2) -> None\n" # noqa: E501 line too long + == "method(self: pybind11_tests.kwargs_and_defaults.first_arg_kw_only, *, i: int = 1, j: int = 2) -> None\n" ) -def test_positional_only_args(msg): +def test_positional_only_args(): assert m.pos_only_all(1, 2) == (1, 2) assert m.pos_only_all(2, 1) == (2, 1) @@ -283,7 +282,7 @@ def test_positional_only_args(msg): # Mix it with args and kwargs: assert ( m.args_kwonly_full_monty.__doc__ - == "args_kwonly_full_monty(arg0: int = 1, arg1: int = 2, /, j: float = 3.14159, *args, z: int = 42, **kwargs) -> tuple\n" # noqa: E501 line too long + == "args_kwonly_full_monty(arg0: int = 1, arg1: int = 2, /, j: float = 3.14159, *args, z: int = 42, **kwargs) -> tuple\n" ) assert m.args_kwonly_full_monty() == (1, 2, 3.14159, (), 42, {}) assert m.args_kwonly_full_monty(8) == (8, 2, 3.14159, (), 42, {}) @@ -326,18 +325,18 @@ def test_positional_only_args(msg): # https://github.com/pybind/pybind11/pull/3402#issuecomment-963341987 assert ( m.first_arg_kw_only.pos_only.__doc__ - == "pos_only(self: pybind11_tests.kwargs_and_defaults.first_arg_kw_only, /, i: int, j: int) -> None\n" # noqa: E501 line too long + == "pos_only(self: pybind11_tests.kwargs_and_defaults.first_arg_kw_only, /, i: int, j: int) -> None\n" ) def test_signatures(): - assert "kw_only_all(*, i: int, j: int) -> tuple\n" == m.kw_only_all.__doc__ - assert "kw_only_mixed(i: int, *, j: int) -> tuple\n" == m.kw_only_mixed.__doc__ - assert "pos_only_all(i: int, j: int, /) -> tuple\n" == m.pos_only_all.__doc__ - assert "pos_only_mix(i: int, /, j: int) -> tuple\n" == m.pos_only_mix.__doc__ + assert m.kw_only_all.__doc__ == "kw_only_all(*, i: int, j: int) -> tuple\n" + assert m.kw_only_mixed.__doc__ == "kw_only_mixed(i: int, *, j: int) -> tuple\n" + assert m.pos_only_all.__doc__ == "pos_only_all(i: int, j: int, /) -> tuple\n" + assert m.pos_only_mix.__doc__ == "pos_only_mix(i: int, /, j: int) -> tuple\n" assert ( - "pos_kw_only_mix(i: int, /, j: int, *, k: int) -> tuple\n" - == m.pos_kw_only_mix.__doc__ + m.pos_kw_only_mix.__doc__ + == "pos_kw_only_mix(i: int, /, j: int, *, k: int) -> tuple\n" ) diff --git a/pybind11/tests/test_local_bindings.py b/pybind11/tests/test_local_bindings.py index 654d96d49..d64187739 100644 --- a/pybind11/tests/test_local_bindings.py +++ b/pybind11/tests/test_local_bindings.py @@ -130,7 +130,8 @@ def test_stl_bind_global(): def test_mixed_local_global(): """Local types take precedence over globally registered types: a module with a `module_local` type can be registered even if the type is already registered globally. With the module, - casting will go to the local type; outside the module casting goes to the global type.""" + casting will go to the local type; outside the module casting goes to the global type. + """ import pybind11_cross_module_tests as cm m.register_mixed_global() diff --git a/pybind11/tests/test_methods_and_attributes.cpp b/pybind11/tests/test_methods_and_attributes.cpp index 815dd5e98..31d46eb7e 100644 --- a/pybind11/tests/test_methods_and_attributes.cpp +++ b/pybind11/tests/test_methods_and_attributes.cpp @@ -177,6 +177,38 @@ struct RValueRefParam { std::size_t func4(std::string &&s) const & { return s.size(); } }; +namespace pybind11_tests { +namespace exercise_is_setter { + +struct FieldBase { + int int_value() const { return int_value_; } + + FieldBase &SetIntValue(int int_value) { + int_value_ = int_value; + return *this; + } + +private: + int int_value_ = -99; +}; + +struct Field : FieldBase {}; + +void add_bindings(py::module &m) { + py::module sm = m.def_submodule("exercise_is_setter"); + // NOTE: FieldBase is not wrapped, therefore ... + py::class_(sm, "Field") + .def(py::init<>()) + .def_property( + "int_value", + &Field::int_value, + &Field::SetIntValue // ... the `FieldBase &` return value here cannot be converted. + ); +} + +} // namespace exercise_is_setter +} // namespace pybind11_tests + TEST_SUBMODULE(methods_and_attributes, m) { // test_methods_and_attributes py::class_ emna(m, "ExampleMandA"); @@ -456,4 +488,6 @@ TEST_SUBMODULE(methods_and_attributes, m) { .def("func2", &RValueRefParam::func2) .def("func3", &RValueRefParam::func3) .def("func4", &RValueRefParam::func4); + + pybind11_tests::exercise_is_setter::add_bindings(m); } diff --git a/pybind11/tests/test_methods_and_attributes.py b/pybind11/tests/test_methods_and_attributes.py index 0a2ae1239..955a85f67 100644 --- a/pybind11/tests/test_methods_and_attributes.py +++ b/pybind11/tests/test_methods_and_attributes.py @@ -183,9 +183,9 @@ def test_static_properties(): # Only static attributes can be deleted del m.TestPropertiesOverride.def_readonly_static + assert hasattr(m.TestPropertiesOverride, "def_readonly_static") assert ( - hasattr(m.TestPropertiesOverride, "def_readonly_static") - and m.TestPropertiesOverride.def_readonly_static + m.TestPropertiesOverride.def_readonly_static is m.TestProperties.def_readonly_static ) assert "def_readonly_static" not in m.TestPropertiesOverride.__dict__ @@ -256,10 +256,7 @@ def test_no_mixed_overloads(): @pytest.mark.parametrize("access", ["ro", "rw", "static_ro", "static_rw"]) def test_property_return_value_policies(access): - if not access.startswith("static"): - obj = m.TestPropRVP() - else: - obj = m.TestPropRVP + obj = m.TestPropRVP() if not access.startswith("static") else m.TestPropRVP ref = getattr(obj, access + "_ref") assert ref.value == 1 @@ -525,3 +522,12 @@ def test_rvalue_ref_param(): assert r.func2("1234") == 4 assert r.func3("12345") == 5 assert r.func4("123456") == 6 + + +def test_is_setter(): + fld = m.exercise_is_setter.Field() + assert fld.int_value == -99 + setter_return = fld.int_value = 100 + assert isinstance(setter_return, int) + assert setter_return == 100 + assert fld.int_value == 100 diff --git a/pybind11/tests/test_modules.py b/pybind11/tests/test_modules.py index e11d68e78..2f6d825b7 100644 --- a/pybind11/tests/test_modules.py +++ b/pybind11/tests/test_modules.py @@ -1,3 +1,5 @@ +import builtins + import pytest import env @@ -61,7 +63,6 @@ def test_importing(): from pybind11_tests.modules import OD assert OD is OrderedDict - assert str(OD([(1, "a"), (2, "b")])) == "OrderedDict([(1, 'a'), (2, 'b')])" def test_pydoc(): @@ -86,12 +87,7 @@ def test_builtin_key_type(): Previous versions of pybind11 would add a unicode key in python 2. """ - if hasattr(__builtins__, "keys"): - keys = __builtins__.keys() - else: # this is to make pypy happy since builtins is different there. - keys = __builtins__.__dict__.keys() - - assert {type(k) for k in keys} == {str} + assert all(type(k) == str for k in dir(builtins)) @pytest.mark.xfail("env.PYPY", reason="PyModule_GetName()") @@ -107,11 +103,10 @@ def test_def_submodule_failures(): sm_name_orig = sm.__name__ sm.__name__ = malformed_utf8 try: - with pytest.raises(Exception): - # Seen with Python 3.9: SystemError: nameless module - # But we do not want to exercise the internals of PyModule_GetName(), which could - # change in future versions of Python, but a bad __name__ is very likely to cause - # some kind of failure indefinitely. + # We want to assert that a bad __name__ causes some kind of failure, although we do not want to exercise + # the internals of PyModule_GetName(). Currently all supported Python versions raise SystemError. If that + # changes in future Python versions, simply add the new expected exception types here. + with pytest.raises(SystemError): m.def_submodule(sm, b"SubSubModuleName") finally: # Clean up to ensure nothing gets upset by a module with an invalid __name__. diff --git a/pybind11/tests/test_numpy_array.cpp b/pybind11/tests/test_numpy_array.cpp index 69ddbe1ef..8c122a865 100644 --- a/pybind11/tests/test_numpy_array.cpp +++ b/pybind11/tests/test_numpy_array.cpp @@ -521,4 +521,32 @@ TEST_SUBMODULE(numpy_array, sm) { sm.def("test_fmt_desc_double", [](const py::array_t &) {}); sm.def("test_fmt_desc_const_float", [](const py::array_t &) {}); sm.def("test_fmt_desc_const_double", [](const py::array_t &) {}); + + sm.def("round_trip_float", [](double d) { return d; }); + + sm.def("pass_array_pyobject_ptr_return_sum_str_values", + [](const py::array_t &objs) { + std::string sum_str_values; + for (const auto &obj : objs) { + sum_str_values += py::str(obj.attr("value")); + } + return sum_str_values; + }); + + sm.def("pass_array_pyobject_ptr_return_as_list", + [](const py::array_t &objs) -> py::list { return objs; }); + + sm.def("return_array_pyobject_ptr_cpp_loop", [](const py::list &objs) { + py::size_t arr_size = py::len(objs); + py::array_t arr_from_list(static_cast(arr_size)); + PyObject **data = arr_from_list.mutable_data(); + for (py::size_t i = 0; i < arr_size; i++) { + assert(data[i] == nullptr); + data[i] = py::cast(objs[i].attr("value")); + } + return arr_from_list; + }); + + sm.def("return_array_pyobject_ptr_from_list", + [](const py::list &objs) -> py::array_t { return objs; }); } diff --git a/pybind11/tests/test_numpy_array.py b/pybind11/tests/test_numpy_array.py index 504963b16..12e7d17d1 100644 --- a/pybind11/tests/test_numpy_array.py +++ b/pybind11/tests/test_numpy_array.py @@ -22,7 +22,7 @@ def test_dtypes(): ) -@pytest.fixture(scope="function") +@pytest.fixture() def arr(): return np.array([[1, 2, 3], [4, 5, 6]], "=u2") @@ -67,7 +67,7 @@ def test_array_attributes(): @pytest.mark.parametrize( - "args, ret", [([], 0), ([0], 0), ([1], 3), ([0, 1], 1), ([1, 2], 5)] + ("args", "ret"), [([], 0), ([0], 0), ([1], 3), ([0, 1], 1), ([1, 2], 5)] ) def test_index_offset(arr, args, ret): assert m.index_at(arr, *args) == ret @@ -93,7 +93,7 @@ def test_dim_check_fail(arr): @pytest.mark.parametrize( - "args, ret", + ("args", "ret"), [ ([], [1, 2, 3, 4, 5, 6]), ([1], [4, 5, 6]), @@ -211,12 +211,14 @@ def test_wrap(): assert b[0, 0] == 1234 a1 = np.array([1, 2], dtype=np.int16) - assert a1.flags.owndata and a1.base is None + assert a1.flags.owndata + assert a1.base is None a2 = m.wrap(a1) assert_references(a1, a2) a1 = np.array([[1, 2], [3, 4]], dtype=np.float32, order="F") - assert a1.flags.owndata and a1.base is None + assert a1.flags.owndata + assert a1.base is None a2 = m.wrap(a1) assert_references(a1, a2) @@ -451,13 +453,15 @@ def test_array_resize(): try: m.array_resize3(a, 3, True) except ValueError as e: - assert str(e).startswith("cannot resize an array") + assert str(e).startswith("cannot resize an array") # noqa: PT017 # transposed array doesn't own data b = a.transpose() try: m.array_resize3(b, 3, False) except ValueError as e: - assert str(e).startswith("cannot resize this array: it does not own its data") + assert str(e).startswith( # noqa: PT017 + "cannot resize this array: it does not own its data" + ) # ... but reshape should be fine m.array_reshape2(b) assert b.shape == (8, 8) @@ -585,3 +589,80 @@ def test_dtype_refcount_leak(): m.ndim(a) after = getrefcount(dtype) assert after == before + + +def test_round_trip_float(): + arr = np.zeros((), np.float64) + arr[()] = 37.2 + assert m.round_trip_float(arr) == 37.2 + + +# HINT: An easy and robust way (although only manual unfortunately) to check for +# ref-count leaks in the test_.*pyobject_ptr.* functions below is to +# * temporarily insert `while True:` (one-by-one), +# * run this test, and +# * run the Linux `top` command in another shell to visually monitor +# `RES` for a minute or two. +# If there is a leak, it is usually evident in seconds because the `RES` +# value increases without bounds. (Don't forget to Ctrl-C the test!) + + +# For use as a temporary user-defined object, to maximize sensitivity of the tests below: +# * Ref-count leaks will be immediately evident. +# * Sanitizers are much more likely to detect heap-use-after-free due to +# other ref-count bugs. +class PyValueHolder: + def __init__(self, value): + self.value = value + + +def WrapWithPyValueHolder(*values): + return [PyValueHolder(v) for v in values] + + +def UnwrapPyValueHolder(vhs): + return [vh.value for vh in vhs] + + +def test_pass_array_pyobject_ptr_return_sum_str_values_ndarray(): + # Intentionally all temporaries, do not change. + assert ( + m.pass_array_pyobject_ptr_return_sum_str_values( + np.array(WrapWithPyValueHolder(-3, "four", 5.0), dtype=object) + ) + == "-3four5.0" + ) + + +def test_pass_array_pyobject_ptr_return_sum_str_values_list(): + # Intentionally all temporaries, do not change. + assert ( + m.pass_array_pyobject_ptr_return_sum_str_values( + WrapWithPyValueHolder(2, "three", -4.0) + ) + == "2three-4.0" + ) + + +def test_pass_array_pyobject_ptr_return_as_list(): + # Intentionally all temporaries, do not change. + assert UnwrapPyValueHolder( + m.pass_array_pyobject_ptr_return_as_list( + np.array(WrapWithPyValueHolder(-1, "two", 3.0), dtype=object) + ) + ) == [-1, "two", 3.0] + + +@pytest.mark.parametrize( + ("return_array_pyobject_ptr", "unwrap"), + [ + (m.return_array_pyobject_ptr_cpp_loop, list), + (m.return_array_pyobject_ptr_from_list, UnwrapPyValueHolder), + ], +) +def test_return_array_pyobject_ptr_cpp_loop(return_array_pyobject_ptr, unwrap): + # Intentionally all temporaries, do not change. + arr_from_list = return_array_pyobject_ptr(WrapWithPyValueHolder(6, "seven", -8.0)) + assert isinstance(arr_from_list, np.ndarray) + assert arr_from_list.dtype == np.dtype("O") + assert unwrap(arr_from_list) == [6, "seven", -8.0] diff --git a/pybind11/tests/test_numpy_dtypes.py b/pybind11/tests/test_numpy_dtypes.py index fcfd587b1..d10457eeb 100644 --- a/pybind11/tests/test_numpy_dtypes.py +++ b/pybind11/tests/test_numpy_dtypes.py @@ -130,14 +130,10 @@ def test_dtype(simple_dtype): partial_nested_fmt(), "[('a','S3'),('b','S3')]", ( - "{{'names':['a','b','c','d']," - + "'formats':[('S4',(3,)),('" - + e - + "i4',(2,)),('u1',(3,)),('" - + e - + "f4',(4,2))]," - + "'offsets':[0,12,20,24],'itemsize':56}}" - ).format(e=e), + "{'names':['a','b','c','d']," + f"'formats':[('S4',(3,)),('{e}i4',(2,)),('u1',(3,)),('{e}f4',(4,2))]," + "'offsets':[0,12,20,24],'itemsize':56}" + ), "[('e1','" + e + "i8'),('e2','u1')]", "[('x','i1'),('y','" + e + "u8')]", "[('cflt','" + e + "c8'),('cdbl','" + e + "c16')]", @@ -291,19 +287,17 @@ def test_array_array(): arr = m.create_array_array(3) assert str(arr.dtype).replace(" ", "") == ( - "{{'names':['a','b','c','d']," - + "'formats':[('S4',(3,)),('" - + e - + "i4',(2,)),('u1',(3,)),('{e}f4',(4,2))]," - + "'offsets':[0,12,20,24],'itemsize':56}}" - ).format(e=e) + "{'names':['a','b','c','d']," + f"'formats':[('S4',(3,)),('{e}i4',(2,)),('u1',(3,)),('{e}f4',(4,2))]," + "'offsets':[0,12,20,24],'itemsize':56}" + ) assert m.print_array_array(arr) == [ "a={{A,B,C,D},{K,L,M,N},{U,V,W,X}},b={0,1}," - + "c={0,1,2},d={{0,1},{10,11},{20,21},{30,31}}", + "c={0,1,2},d={{0,1},{10,11},{20,21},{30,31}}", "a={{W,X,Y,Z},{G,H,I,J},{Q,R,S,T}},b={1000,1001}," - + "c={10,11,12},d={{100,101},{110,111},{120,121},{130,131}}", + "c={10,11,12},d={{100,101},{110,111},{120,121},{130,131}}", "a={{S,T,U,V},{C,D,E,F},{M,N,O,P}},b={2000,2001}," - + "c={20,21,22},d={{200,201},{210,211},{220,221},{230,231}}", + "c={20,21,22},d={{200,201},{210,211},{220,221},{230,231}}", ] assert arr["a"].tolist() == [ [b"ABCD", b"KLMN", b"UVWX"], diff --git a/pybind11/tests/test_numpy_vectorize.py b/pybind11/tests/test_numpy_vectorize.py index 7e8c015c4..f1e8b6254 100644 --- a/pybind11/tests/test_numpy_vectorize.py +++ b/pybind11/tests/test_numpy_vectorize.py @@ -149,7 +149,7 @@ def test_docs(doc): doc(m.vectorized_func) == """ vectorized_func(arg0: numpy.ndarray[numpy.int32], arg1: numpy.ndarray[numpy.float32], arg2: numpy.ndarray[numpy.float64]) -> object - """ # noqa: E501 line too long + """ ) diff --git a/pybind11/tests/test_operator_overloading.cpp b/pybind11/tests/test_operator_overloading.cpp index a4b895a89..112a363b4 100644 --- a/pybind11/tests/test_operator_overloading.cpp +++ b/pybind11/tests/test_operator_overloading.cpp @@ -132,22 +132,18 @@ struct hash { // Not a good abs function, but easy to test. std::string abs(const Vector2 &) { return "abs(Vector2)"; } -// MSVC & Intel warns about unknown pragmas, and warnings are errors. -#if !defined(_MSC_VER) && !defined(__INTEL_COMPILER) -# pragma GCC diagnostic push // clang 7.0.0 and Apple LLVM 10.0.1 introduce `-Wself-assign-overloaded` to // `-Wall`, which is used here for overloading (e.g. `py::self += py::self `). -// Here, we suppress the warning using `#pragma diagnostic`. +// Here, we suppress the warning // Taken from: https://github.com/RobotLocomotion/drake/commit/aaf84b46 // TODO(eric): This could be resolved using a function / functor (e.g. `py::self()`). -# if defined(__APPLE__) && defined(__clang__) -# if (__clang_major__ >= 10) -# pragma GCC diagnostic ignored "-Wself-assign-overloaded" -# endif -# elif defined(__clang__) -# if (__clang_major__ >= 7) -# pragma GCC diagnostic ignored "-Wself-assign-overloaded" -# endif +#if defined(__APPLE__) && defined(__clang__) +# if (__clang_major__ >= 10) +PYBIND11_WARNING_DISABLE_CLANG("-Wself-assign-overloaded") +# endif +#elif defined(__clang__) +# if (__clang_major__ >= 7) +PYBIND11_WARNING_DISABLE_CLANG("-Wself-assign-overloaded") # endif #endif @@ -283,6 +279,3 @@ TEST_SUBMODULE(operators, m) { m.def("get_unhashable_HashMe_set", []() { return std::unordered_set{{"one"}}; }); } -#if !defined(_MSC_VER) && !defined(__INTEL_COMPILER) -# pragma GCC diagnostic pop -#endif diff --git a/pybind11/tests/test_operator_overloading.py b/pybind11/tests/test_operator_overloading.py index b228da3cc..9fde305a0 100644 --- a/pybind11/tests/test_operator_overloading.py +++ b/pybind11/tests/test_operator_overloading.py @@ -130,7 +130,6 @@ def test_nested(): def test_overriding_eq_reset_hash(): - assert m.Comparable(15) is not m.Comparable(15) assert m.Comparable(15) == m.Comparable(15) diff --git a/pybind11/tests/test_pytypes.cpp b/pybind11/tests/test_pytypes.cpp index f532e2608..b4ee64289 100644 --- a/pybind11/tests/test_pytypes.cpp +++ b/pybind11/tests/test_pytypes.cpp @@ -99,6 +99,8 @@ void m_defs(py::module_ &m) { } // namespace handle_from_move_only_type_with_operator_PyObject TEST_SUBMODULE(pytypes, m) { + m.def("obj_class_name", [](py::handle obj) { return py::detail::obj_class_name(obj.ptr()); }); + handle_from_move_only_type_with_operator_PyObject::m_defs(m); // test_bool @@ -109,6 +111,11 @@ TEST_SUBMODULE(pytypes, m) { m.def("get_iterator", [] { return py::iterator(); }); // test_iterable m.def("get_iterable", [] { return py::iterable(); }); + m.def("get_frozenset_from_iterable", + [](const py::iterable &iter) { return py::frozenset(iter); }); + m.def("get_list_from_iterable", [](const py::iterable &iter) { return py::list(iter); }); + m.def("get_set_from_iterable", [](const py::iterable &iter) { return py::set(iter); }); + m.def("get_tuple_from_iterable", [](const py::iterable &iter) { return py::tuple(iter); }); // test_float m.def("get_float", [] { return py::float_(0.0f); }); // test_list @@ -178,7 +185,7 @@ TEST_SUBMODULE(pytypes, m) { return d2; }); m.def("dict_contains", - [](const py::dict &dict, py::object val) { return dict.contains(val); }); + [](const py::dict &dict, const py::object &val) { return dict.contains(val); }); m.def("dict_contains", [](const py::dict &dict, const char *val) { return dict.contains(val); }); @@ -201,7 +208,12 @@ TEST_SUBMODULE(pytypes, m) { m.def("str_from_char_ssize_t", []() { return py::str{"red", (py::ssize_t) 3}; }); m.def("str_from_char_size_t", []() { return py::str{"blue", (py::size_t) 4}; }); m.def("str_from_string", []() { return py::str(std::string("baz")); }); + m.def("str_from_std_string_input", [](const std::string &stri) { return py::str(stri); }); + m.def("str_from_cstr_input", [](const char *c_str) { return py::str(c_str); }); m.def("str_from_bytes", []() { return py::str(py::bytes("boo", 3)); }); + m.def("str_from_bytes_input", + [](const py::bytes &encoded_str) { return py::str(encoded_str); }); + m.def("str_from_object", [](const py::object &obj) { return py::str(obj); }); m.def("repr_from_object", [](const py::object &obj) { return py::repr(obj); }); m.def("str_from_handle", [](py::handle h) { return py::str(h); }); @@ -248,6 +260,15 @@ TEST_SUBMODULE(pytypes, m) { }); }); + m.def("return_capsule_with_destructor_3", []() { + py::print("creating capsule"); + auto cap = py::capsule((void *) 1233, "oname", [](void *ptr) { + py::print("destructing capsule: {}"_s.format((size_t) ptr)); + }); + py::print("original name: {}"_s.format(cap.name())); + return cap; + }); + m.def("return_renamed_capsule_with_destructor_2", []() { py::print("creating capsule"); auto cap = py::capsule((void *) 1234, [](void *ptr) { @@ -284,6 +305,12 @@ TEST_SUBMODULE(pytypes, m) { return capsule; }); + m.def("return_capsule_with_explicit_nullptr_dtor", []() { + py::print("creating capsule with explicit nullptr dtor"); + return py::capsule(reinterpret_cast(1234), + static_cast(nullptr)); // PR #4221 + }); + // test_accessors m.def("accessor_api", [](const py::object &o) { auto d = py::dict(); @@ -527,6 +554,9 @@ TEST_SUBMODULE(pytypes, m) { m.def("hash_function", [](py::object obj) { return py::hash(std::move(obj)); }); + m.def("obj_contains", + [](py::object &obj, const py::object &key) { return obj.contains(key); }); + m.def("test_number_protocol", [](const py::object &a, const py::object &b) { py::list l; l.append(a.equal(b)); @@ -756,4 +786,38 @@ TEST_SUBMODULE(pytypes, m) { } return o; }); + + // testing immutable object augmented assignment: #issue 3812 + m.def("inplace_append", [](py::object &a, const py::object &b) { + a += b; + return a; + }); + m.def("inplace_subtract", [](py::object &a, const py::object &b) { + a -= b; + return a; + }); + m.def("inplace_multiply", [](py::object &a, const py::object &b) { + a *= b; + return a; + }); + m.def("inplace_divide", [](py::object &a, const py::object &b) { + a /= b; + return a; + }); + m.def("inplace_or", [](py::object &a, const py::object &b) { + a |= b; + return a; + }); + m.def("inplace_and", [](py::object &a, const py::object &b) { + a &= b; + return a; + }); + m.def("inplace_lshift", [](py::object &a, const py::object &b) { + a <<= b; + return a; + }); + m.def("inplace_rshift", [](py::object &a, const py::object &b) { + a >>= b; + return a; + }); } diff --git a/pybind11/tests/test_pytypes.py b/pybind11/tests/test_pytypes.py index 7a0a8b4ab..eda7a20a9 100644 --- a/pybind11/tests/test_pytypes.py +++ b/pybind11/tests/test_pytypes.py @@ -9,7 +9,13 @@ from pybind11_tests import detailed_error_messages_enabled from pybind11_tests import pytypes as m -def test_handle_from_move_only_type_with_operator_PyObject(): # noqa: N802 +def test_obj_class_name(): + assert m.obj_class_name(None) == "NoneType" + assert m.obj_class_name(list) == "list" + assert m.obj_class_name([]) == "list" + + +def test_handle_from_move_only_type_with_operator_PyObject(): assert m.handle_from_move_only_type_with_operator_PyObject_ncnst() assert m.handle_from_move_only_type_with_operator_PyObject_const() @@ -26,6 +32,22 @@ def test_iterator(doc): assert doc(m.get_iterator) == "get_iterator() -> Iterator" +@pytest.mark.parametrize( + ("pytype", "from_iter_func"), + [ + (frozenset, m.get_frozenset_from_iterable), + (list, m.get_list_from_iterable), + (set, m.get_set_from_iterable), + (tuple, m.get_tuple_from_iterable), + ], +) +def test_from_iterable(pytype, from_iter_func): + my_iter = iter(range(10)) + s = from_iter_func(my_iter) + assert type(s) == pytype + assert s == pytype(range(10)) + + def test_iterable(doc): assert doc(m.get_iterable) == "get_iterable() -> Iterable" @@ -65,7 +87,7 @@ def test_list(capture, doc): assert doc(m.print_list) == "print_list(arg0: list) -> None" -def test_none(capture, doc): +def test_none(doc): assert doc(m.get_none) == "get_none() -> None" assert doc(m.print_none) == "print_none(arg0: None) -> None" @@ -152,6 +174,31 @@ def test_dict(capture, doc): assert m.dict_keyword_constructor() == {"x": 1, "y": 2, "z": 3} +class CustomContains: + d = {"key": None} + + def __contains__(self, m): + return m in self.d + + +@pytest.mark.parametrize( + ("arg", "func"), + [ + (set(), m.anyset_contains), + ({}, m.dict_contains), + (CustomContains(), m.obj_contains), + ], +) +@pytest.mark.xfail("env.PYPY and sys.pypy_version_info < (7, 3, 10)", strict=False) +def test_unhashable_exceptions(arg, func): + class Unhashable: + __hash__ = None + + with pytest.raises(TypeError) as exc_info: + func(arg, Unhashable()) + assert "unhashable type:" in str(exc_info.value) + + def test_tuple(): assert m.tuple_no_args() == () assert m.tuple_ssize_t() == () @@ -203,6 +250,20 @@ def test_str(doc): m.str_from_string_from_str(ucs_surrogates_str) +@pytest.mark.parametrize( + "func", + [ + m.str_from_bytes_input, + m.str_from_cstr_input, + m.str_from_std_string_input, + ], +) +def test_surrogate_pairs_unicode_error(func): + input_str = "\ud83d\ude4f".encode("utf-8", "surrogatepass") + with pytest.raises(UnicodeDecodeError): + func(input_str) + + def test_bytes(doc): assert m.bytes_from_char_ssize_t().decode() == "green" assert m.bytes_from_char_size_t().decode() == "purple" @@ -212,7 +273,7 @@ def test_bytes(doc): assert doc(m.bytes_from_str) == "bytes_from_str() -> bytes" -def test_bytearray(doc): +def test_bytearray(): assert m.bytearray_from_char_ssize_t().decode() == "$%" assert m.bytearray_from_char_size_t().decode() == "@$!" assert m.bytearray_from_string().decode() == "foo" @@ -258,6 +319,19 @@ def test_capsule(capture): """ ) + with capture: + a = m.return_capsule_with_destructor_3() + del a + pytest.gc_collect() + assert ( + capture.unordered + == """ + creating capsule + destructing capsule: 1233 + original name: oname + """ + ) + with capture: a = m.return_renamed_capsule_with_destructor_2() del a @@ -283,6 +357,17 @@ def test_capsule(capture): """ ) + with capture: + a = m.return_capsule_with_explicit_nullptr_dtor() + del a + pytest.gc_collect() + assert ( + capture.unordered + == """ + creating capsule with explicit nullptr dtor + """ + ) + def test_accessors(): class SubTestObject: @@ -313,7 +398,7 @@ def test_accessors(): assert d["implicit_list"] == [1, 2, 3] assert all(x in TestObject.__dict__ for x in d["implicit_dict"]) - assert m.tuple_accessor(tuple()) == (0, 1, 2) + assert m.tuple_accessor(()) == (0, 1, 2) d = m.accessor_assignment() assert d["get"] == 0 @@ -403,7 +488,7 @@ def test_pybind11_str_raw_str(): assert cvt({}) == "{}" assert cvt({3: 4}) == "{3: 4}" assert cvt(set()) == "set()" - assert cvt({3, 3}) == "{3}" + assert cvt({3}) == "{3}" valid_orig = "DZ" valid_utf8 = valid_orig.encode("utf-8") @@ -464,7 +549,7 @@ def test_print(capture): assert str(excinfo.value) == "Unable to convert call argument " + ( "'1' of type 'UnregisteredType' to Python object" if detailed_error_messages_enabled - else "to Python object (#define PYBIND11_DETAILED_ERROR_MESSAGES or compile in debug mode for details)" + else "'1' to Python object (#define PYBIND11_DETAILED_ERROR_MESSAGES or compile in debug mode for details)" ) @@ -521,7 +606,7 @@ def test_issue2361(): @pytest.mark.parametrize( - "method, args, fmt, expected_view", + ("method", "args", "fmt", "expected_view"), [ (m.test_memoryview_object, (b"red",), "B", b"red"), (m.test_memoryview_buffer_info, (b"green",), "B", b"green"), @@ -579,7 +664,7 @@ def test_memoryview_from_memory(): def test_builtin_functions(): - assert m.get_len([i for i in range(42)]) == 42 + assert m.get_len(list(range(42))) == 42 with pytest.raises(TypeError) as exc_info: m.get_len(i for i in range(42)) assert str(exc_info.value) in [ @@ -623,7 +708,7 @@ def test_pass_bytes_or_unicode_to_string_types(): @pytest.mark.parametrize( - "create_weakref, create_weakref_with_callback", + ("create_weakref", "create_weakref_with_callback"), [ (m.weakref_from_handle, m.weakref_from_handle_and_function), (m.weakref_from_object, m.weakref_from_object_and_function), @@ -638,7 +723,7 @@ def test_weakref(create_weakref, create_weakref_with_callback): callback_called = False - def callback(wr): + def callback(_): nonlocal callback_called callback_called = True @@ -658,7 +743,7 @@ def test_weakref(create_weakref, create_weakref_with_callback): @pytest.mark.parametrize( - "create_weakref, has_callback", + ("create_weakref", "has_callback"), [ (m.weakref_from_handle, False), (m.weakref_from_object, False), @@ -676,10 +761,7 @@ def test_weakref_err(create_weakref, has_callback): ob = C() # Should raise TypeError on CPython with pytest.raises(TypeError) if not env.PYPY else contextlib.nullcontext(): - if has_callback: - _ = create_weakref(ob, callback) - else: - _ = create_weakref(ob) + _ = create_weakref(ob, callback) if has_callback else create_weakref(ob) def test_cpp_iterators(): @@ -739,3 +821,78 @@ def test_populate_obj_str_attrs(): new_attrs = {k: v for k, v in new_o.__dict__.items() if not k.startswith("_")} assert all(isinstance(v, str) for v in new_attrs.values()) assert len(new_attrs) == pop + + +@pytest.mark.parametrize( + ("a", "b"), + [("foo", "bar"), (1, 2), (1.0, 2.0), (list(range(3)), list(range(3, 6)))], +) +def test_inplace_append(a, b): + expected = a + b + assert m.inplace_append(a, b) == expected + + +@pytest.mark.parametrize( + ("a", "b"), [(3, 2), (3.0, 2.0), (set(range(3)), set(range(2)))] +) +def test_inplace_subtract(a, b): + expected = a - b + assert m.inplace_subtract(a, b) == expected + + +@pytest.mark.parametrize(("a", "b"), [(3, 2), (3.0, 2.0), ([1], 3)]) +def test_inplace_multiply(a, b): + expected = a * b + assert m.inplace_multiply(a, b) == expected + + +@pytest.mark.parametrize(("a", "b"), [(6, 3), (6.0, 3.0)]) +def test_inplace_divide(a, b): + expected = a / b + assert m.inplace_divide(a, b) == expected + + +@pytest.mark.parametrize( + ("a", "b"), + [ + (False, True), + ( + set(), + { + 1, + }, + ), + ], +) +def test_inplace_or(a, b): + expected = a | b + assert m.inplace_or(a, b) == expected + + +@pytest.mark.parametrize( + ("a", "b"), + [ + (True, False), + ( + {1, 2, 3}, + { + 1, + }, + ), + ], +) +def test_inplace_and(a, b): + expected = a & b + assert m.inplace_and(a, b) == expected + + +@pytest.mark.parametrize(("a", "b"), [(8, 1), (-3, 2)]) +def test_inplace_lshift(a, b): + expected = a << b + assert m.inplace_lshift(a, b) == expected + + +@pytest.mark.parametrize(("a", "b"), [(8, 1), (-2, 2)]) +def test_inplace_rshift(a, b): + expected = a >> b + assert m.inplace_rshift(a, b) == expected diff --git a/pybind11/tests/test_sequences_and_iterators.cpp b/pybind11/tests/test_sequences_and_iterators.cpp index b867f49a2..1de65edbf 100644 --- a/pybind11/tests/test_sequences_and_iterators.cpp +++ b/pybind11/tests/test_sequences_and_iterators.cpp @@ -559,4 +559,23 @@ TEST_SUBMODULE(sequences_and_iterators, m) { []() { return py::make_iterator(list); }); m.def("make_iterator_2", []() { return py::make_iterator(list); }); + + // test_iterator on c arrays + // #4100: ensure lvalue required as increment operand + class CArrayHolder { + public: + CArrayHolder(double x, double y, double z) { + values[0] = x; + values[1] = y; + values[2] = z; + }; + double values[3]; + }; + + py::class_(m, "CArrayHolder") + .def(py::init()) + .def( + "__iter__", + [](const CArrayHolder &v) { return py::make_iterator(v.values, v.values + 3); }, + py::keep_alive<0, 1>()); } diff --git a/pybind11/tests/test_sequences_and_iterators.py b/pybind11/tests/test_sequences_and_iterators.py index 062e3b3d3..dc129f2bf 100644 --- a/pybind11/tests/test_sequences_and_iterators.py +++ b/pybind11/tests/test_sequences_and_iterators.py @@ -1,5 +1,5 @@ import pytest -from pytest import approx +from pytest import approx # noqa: PT013 from pybind11_tests import ConstructorStats from pybind11_tests import sequences_and_iterators as m @@ -103,7 +103,8 @@ def test_sequence(): assert "Sequence" in repr(s) assert len(s) == 5 - assert s[0] == 0 and s[3] == 0 + assert s[0] == 0 + assert s[3] == 0 assert 12.34 not in s s[0], s[3] = 12.34, 56.78 assert 12.34 in s @@ -241,3 +242,11 @@ def test_iterator_rvp(): assert list(m.make_iterator_1()) == [1, 2, 3] assert list(m.make_iterator_2()) == [1, 2, 3] assert not isinstance(m.make_iterator_1(), type(m.make_iterator_2())) + + +def test_carray_iterator(): + """#4100: Check for proper iterator overload with C-Arrays""" + args_gt = [float(i) for i in range(3)] + arr_h = m.CArrayHolder(*args_gt) + args = list(arr_h) + assert args_gt == args diff --git a/pybind11/tests/test_smart_ptr.cpp b/pybind11/tests/test_smart_ptr.cpp index 2c23a9a19..6d9efcedc 100644 --- a/pybind11/tests/test_smart_ptr.cpp +++ b/pybind11/tests/test_smart_ptr.cpp @@ -266,14 +266,14 @@ struct ElementList { // It is always possible to construct a ref from an Object* pointer without // possible inconsistencies, hence the 'true' argument at the end. // Make pybind11 aware of the non-standard getter member function -namespace pybind11 { +namespace PYBIND11_NAMESPACE { namespace detail { template struct holder_helper> { static const T *get(const ref &p) { return p.get_ptr(); } }; } // namespace detail -} // namespace pybind11 +} // namespace PYBIND11_NAMESPACE // Make pybind aware of the ref-counted wrapper type (s): PYBIND11_DECLARE_HOLDER_TYPE(T, ref, true); diff --git a/pybind11/tests/test_stl.cpp b/pybind11/tests/test_stl.cpp index 38d32fda9..d45465d68 100644 --- a/pybind11/tests/test_stl.cpp +++ b/pybind11/tests/test_stl.cpp @@ -23,7 +23,7 @@ #if defined(PYBIND11_TEST_BOOST) # include -namespace pybind11 { +namespace PYBIND11_NAMESPACE { namespace detail { template struct type_caster> : optional_caster> {}; @@ -31,7 +31,7 @@ struct type_caster> : optional_caster> {}; template <> struct type_caster : void_caster {}; } // namespace detail -} // namespace pybind11 +} // namespace PYBIND11_NAMESPACE #endif // Test with `std::variant` in C++17 mode, or with `boost::variant` in C++11/14 @@ -43,7 +43,7 @@ using std::variant; # define PYBIND11_TEST_VARIANT 1 using boost::variant; -namespace pybind11 { +namespace PYBIND11_NAMESPACE { namespace detail { template struct type_caster> : variant_caster> {}; @@ -56,7 +56,7 @@ struct visit_helper { } }; } // namespace detail -} // namespace pybind11 +} // namespace PYBIND11_NAMESPACE #endif PYBIND11_MAKE_OPAQUE(std::vector>); @@ -159,13 +159,13 @@ private: std::vector storage; }; -namespace pybind11 { +namespace PYBIND11_NAMESPACE { namespace detail { template struct type_caster> : optional_caster> {}; } // namespace detail -} // namespace pybind11 +} // namespace PYBIND11_NAMESPACE TEST_SUBMODULE(stl, m) { // test_vector @@ -176,9 +176,14 @@ TEST_SUBMODULE(stl, m) { m.def("load_bool_vector", [](const std::vector &v) { return v.at(0) == true && v.at(1) == false; }); // Unnumbered regression (caused by #936): pointers to stl containers aren't castable - static std::vector lvv{2}; m.def( - "cast_ptr_vector", []() { return &lvv; }, py::return_value_policy::reference); + "cast_ptr_vector", + []() { + // Using no-destructor idiom to side-step warnings from overzealous compilers. + static auto *v = new std::vector{2}; + return v; + }, + py::return_value_policy::reference); // test_deque m.def("cast_deque", []() { return std::deque{1}; }); @@ -237,6 +242,7 @@ TEST_SUBMODULE(stl, m) { lvn["b"].emplace_back(); // add a list lvn["b"].back().emplace_back(); // add an array lvn["b"].back().emplace_back(); // add another array + static std::vector lvv{2}; m.def("cast_lv_vector", []() -> const decltype(lvv) & { return lvv; }); m.def("cast_lv_array", []() -> const decltype(lva) & { return lva; }); m.def("cast_lv_map", []() -> const decltype(lvm) & { return lvm; }); diff --git a/pybind11/tests/test_stl.py b/pybind11/tests/test_stl.py index d30c38211..8a614f8b8 100644 --- a/pybind11/tests/test_stl.py +++ b/pybind11/tests/test_stl.py @@ -14,7 +14,7 @@ def test_vector(doc): assert m.cast_bool_vector() == [True, False] assert m.load_bool_vector([True, False]) - assert m.load_bool_vector(tuple([True, False])) + assert m.load_bool_vector((True, False)) assert doc(m.cast_vector) == "cast_vector() -> List[int]" assert doc(m.load_vector) == "load_vector(arg0: List[int]) -> bool" @@ -23,7 +23,7 @@ def test_vector(doc): assert m.cast_ptr_vector() == ["lvalue", "lvalue"] -def test_deque(doc): +def test_deque(): """std::deque <-> list""" lst = m.cast_deque() assert lst == [1] @@ -39,8 +39,11 @@ def test_array(doc): assert m.load_array(lst) assert m.load_array(tuple(lst)) - assert doc(m.cast_array) == "cast_array() -> List[int[2]]" - assert doc(m.load_array) == "load_array(arg0: List[int[2]]) -> bool" + assert doc(m.cast_array) == "cast_array() -> Annotated[List[int], FixedSize(2)]" + assert ( + doc(m.load_array) + == "load_array(arg0: Annotated[List[int], FixedSize(2)]) -> bool" + ) def test_valarray(doc): @@ -95,7 +98,8 @@ def test_recursive_casting(): # Issue #853 test case: z = m.cast_unique_ptr_vector() - assert z[0].value == 7 and z[1].value == 42 + assert z[0].value == 7 + assert z[1].value == 42 def test_move_out_container(): @@ -366,7 +370,7 @@ def test_issue_1561(): """check fix for issue #1561""" bar = m.Issue1561Outer() bar.list = [m.Issue1561Inner("bar")] - bar.list + assert bar.list assert bar.list[0].data == "bar" diff --git a/pybind11/tests/test_stl_binders.cpp b/pybind11/tests/test_stl_binders.cpp index ca9630bd1..1681760aa 100644 --- a/pybind11/tests/test_stl_binders.cpp +++ b/pybind11/tests/test_stl_binders.cpp @@ -70,6 +70,44 @@ NestMap *times_hundred(int n) { return m; } +/* + * Recursive data structures as test for issue #4623 + */ +struct RecursiveVector : std::vector { + using Parent = std::vector; + using Parent::Parent; +}; + +struct RecursiveMap : std::map { + using Parent = std::map; + using Parent::Parent; +}; + +/* + * Pybind11 does not catch more complicated recursion schemes, such as mutual + * recursion. + * In that case custom recursive_container_traits specializations need to be added, + * thus manually telling pybind11 about the recursion. + */ +struct MutuallyRecursiveContainerPairMV; +struct MutuallyRecursiveContainerPairVM; + +struct MutuallyRecursiveContainerPairMV : std::map {}; +struct MutuallyRecursiveContainerPairVM : std::vector {}; + +namespace pybind11 { +namespace detail { +template +struct recursive_container_traits { + using type_to_check_recursively = recursive_bottom; +}; +template +struct recursive_container_traits { + using type_to_check_recursively = recursive_bottom; +}; +} // namespace detail +} // namespace pybind11 + TEST_SUBMODULE(stl_binders, m) { // test_vector_int py::bind_vector>(m, "VectorInt", py::buffer_protocol()); @@ -129,6 +167,12 @@ TEST_SUBMODULE(stl_binders, m) { m, "VectorUndeclStruct", py::buffer_protocol()); }); + // Bind recursive container types + py::bind_vector(m, "RecursiveVector"); + py::bind_map(m, "RecursiveMap"); + py::bind_map(m, "MutuallyRecursiveContainerPairMV"); + py::bind_vector(m, "MutuallyRecursiveContainerPairVM"); + // The rest depends on numpy: try { py::module_::import("numpy"); diff --git a/pybind11/tests/test_stl_binders.py b/pybind11/tests/test_stl_binders.py index d5e9ccced..e002f5b67 100644 --- a/pybind11/tests/test_stl_binders.py +++ b/pybind11/tests/test_stl_binders.py @@ -186,9 +186,9 @@ def test_map_string_double(): um["ua"] = 1.1 um["ub"] = 2.6 - assert sorted(list(um)) == ["ua", "ub"] + assert sorted(um) == ["ua", "ub"] assert list(um.keys()) == list(um) - assert sorted(list(um.items())) == [("ua", 1.1), ("ub", 2.6)] + assert sorted(um.items()) == [("ua", 1.1), ("ub", 2.6)] assert list(zip(um.keys(), um.values())) == list(um.items()) assert "UnorderedMapStringDouble" in str(um) @@ -304,8 +304,52 @@ def test_map_delitem(): um["ua"] = 1.1 um["ub"] = 2.6 - assert sorted(list(um)) == ["ua", "ub"] - assert sorted(list(um.items())) == [("ua", 1.1), ("ub", 2.6)] + assert sorted(um) == ["ua", "ub"] + assert sorted(um.items()) == [("ua", 1.1), ("ub", 2.6)] del um["ua"] - assert sorted(list(um)) == ["ub"] - assert sorted(list(um.items())) == [("ub", 2.6)] + assert sorted(um) == ["ub"] + assert sorted(um.items()) == [("ub", 2.6)] + + +def test_map_view_types(): + map_string_double = m.MapStringDouble() + unordered_map_string_double = m.UnorderedMapStringDouble() + map_string_double_const = m.MapStringDoubleConst() + unordered_map_string_double_const = m.UnorderedMapStringDoubleConst() + + assert map_string_double.keys().__class__.__name__ == "KeysView[str]" + assert map_string_double.values().__class__.__name__ == "ValuesView[float]" + assert map_string_double.items().__class__.__name__ == "ItemsView[str, float]" + + keys_type = type(map_string_double.keys()) + assert type(unordered_map_string_double.keys()) is keys_type + assert type(map_string_double_const.keys()) is keys_type + assert type(unordered_map_string_double_const.keys()) is keys_type + + values_type = type(map_string_double.values()) + assert type(unordered_map_string_double.values()) is values_type + assert type(map_string_double_const.values()) is values_type + assert type(unordered_map_string_double_const.values()) is values_type + + items_type = type(map_string_double.items()) + assert type(unordered_map_string_double.items()) is items_type + assert type(map_string_double_const.items()) is items_type + assert type(unordered_map_string_double_const.items()) is items_type + + +def test_recursive_vector(): + recursive_vector = m.RecursiveVector() + recursive_vector.append(m.RecursiveVector()) + recursive_vector[0].append(m.RecursiveVector()) + recursive_vector[0].append(m.RecursiveVector()) + # Can't use len() since test_stl_binders.cpp does not include stl.h, + # so the necessary conversion is missing + assert recursive_vector[0].count(m.RecursiveVector()) == 2 + + +def test_recursive_map(): + recursive_map = m.RecursiveMap() + recursive_map[100] = m.RecursiveMap() + recursive_map[100][101] = m.RecursiveMap() + recursive_map[100][102] = m.RecursiveMap() + assert list(recursive_map[100].keys()) == [101, 102] diff --git a/pybind11/tests/test_tagbased_polymorphic.cpp b/pybind11/tests/test_tagbased_polymorphic.cpp index 2807e86ba..12ba6532f 100644 --- a/pybind11/tests/test_tagbased_polymorphic.cpp +++ b/pybind11/tests/test_tagbased_polymorphic.cpp @@ -117,7 +117,7 @@ std::string Animal::name_of_kind(Kind kind) { return raw_name; } -namespace pybind11 { +namespace PYBIND11_NAMESPACE { template struct polymorphic_type_hook::value>> { static const void *get(const itype *src, const std::type_info *&type) { @@ -125,7 +125,7 @@ struct polymorphic_type_hook(m, "Animal").def_readonly("name", &Animal::name); diff --git a/pybind11/tests/test_type_caster_pyobject_ptr.cpp b/pybind11/tests/test_type_caster_pyobject_ptr.cpp new file mode 100644 index 000000000..1667ea126 --- /dev/null +++ b/pybind11/tests/test_type_caster_pyobject_ptr.cpp @@ -0,0 +1,130 @@ +#include +#include +#include + +#include "pybind11_tests.h" + +#include +#include + +namespace { + +std::vector make_vector_pyobject_ptr(const py::object &ValueHolder) { + std::vector vec_obj; + for (int i = 1; i < 3; i++) { + vec_obj.push_back(ValueHolder(i * 93).release().ptr()); + } + // This vector now owns the refcounts. + return vec_obj; +} + +} // namespace + +TEST_SUBMODULE(type_caster_pyobject_ptr, m) { + m.def("cast_from_pyobject_ptr", []() { + PyObject *ptr = PyLong_FromLongLong(6758L); + return py::cast(ptr, py::return_value_policy::take_ownership); + }); + m.def("cast_handle_to_pyobject_ptr", [](py::handle obj) { + auto rc1 = obj.ref_count(); + auto *ptr = py::cast(obj); + auto rc2 = obj.ref_count(); + if (rc2 != rc1 + 1) { + return -1; + } + return 100 - py::reinterpret_steal(ptr).attr("value").cast(); + }); + m.def("cast_object_to_pyobject_ptr", [](py::object obj) { + py::handle hdl = obj; + auto rc1 = hdl.ref_count(); + auto *ptr = py::cast(std::move(obj)); + auto rc2 = hdl.ref_count(); + if (rc2 != rc1) { + return -1; + } + return 300 - py::reinterpret_steal(ptr).attr("value").cast(); + }); + m.def("cast_list_to_pyobject_ptr", [](py::list lst) { + // This is to cover types implicitly convertible to object. + py::handle hdl = lst; + auto rc1 = hdl.ref_count(); + auto *ptr = py::cast(std::move(lst)); + auto rc2 = hdl.ref_count(); + if (rc2 != rc1) { + return -1; + } + return 400 - static_cast(py::len(py::reinterpret_steal(ptr))); + }); + + m.def( + "return_pyobject_ptr", + []() { return PyLong_FromLongLong(2314L); }, + py::return_value_policy::take_ownership); + m.def("pass_pyobject_ptr", [](PyObject *ptr) { + return 200 - py::reinterpret_borrow(ptr).attr("value").cast(); + }); + + m.def("call_callback_with_object_return", + [](const std::function &cb, int value) { return cb(value); }); + m.def( + "call_callback_with_pyobject_ptr_return", + [](const std::function &cb, int value) { return cb(value); }, + py::return_value_policy::take_ownership); + m.def( + "call_callback_with_pyobject_ptr_arg", + [](const std::function &cb, py::handle obj) { return cb(obj.ptr()); }, + py::arg("cb"), // This triggers return_value_policy::automatic_reference + py::arg("obj")); + + m.def("cast_to_pyobject_ptr_nullptr", [](bool set_error) { + if (set_error) { + PyErr_SetString(PyExc_RuntimeError, "Reflective of healthy error handling."); + } + PyObject *ptr = nullptr; + py::cast(ptr); + }); + + m.def("cast_to_pyobject_ptr_non_nullptr_with_error_set", []() { + PyErr_SetString(PyExc_RuntimeError, "Reflective of unhealthy error handling."); + py::cast(Py_None); + }); + + m.def("pass_list_pyobject_ptr", [](const std::vector &vec_obj) { + int acc = 0; + for (const auto &ptr : vec_obj) { + acc = acc * 1000 + py::reinterpret_borrow(ptr).attr("value").cast(); + } + return acc; + }); + + m.def("return_list_pyobject_ptr_take_ownership", + make_vector_pyobject_ptr, + // Ownership is transferred one-by-one when the vector is converted to a Python list. + py::return_value_policy::take_ownership); + + m.def("return_list_pyobject_ptr_reference", + make_vector_pyobject_ptr, + // Ownership is not transferred. + py::return_value_policy::reference); + + m.def("dec_ref_each_pyobject_ptr", [](const std::vector &vec_obj) { + std::size_t i = 0; + for (; i < vec_obj.size(); i++) { + py::handle h(vec_obj[i]); + if (static_cast(h.ref_count()) < 2) { + break; // Something is badly wrong. + } + h.dec_ref(); + } + return i; + }); + + m.def("pass_pyobject_ptr_and_int", [](PyObject *, int) {}); + +#ifdef PYBIND11_NO_COMPILE_SECTION // Change to ifndef for manual testing. + { + PyObject *ptr = nullptr; + (void) py::cast(*ptr); + } +#endif +} diff --git a/pybind11/tests/test_type_caster_pyobject_ptr.py b/pybind11/tests/test_type_caster_pyobject_ptr.py new file mode 100644 index 000000000..1f1ece2ba --- /dev/null +++ b/pybind11/tests/test_type_caster_pyobject_ptr.py @@ -0,0 +1,104 @@ +import pytest + +from pybind11_tests import type_caster_pyobject_ptr as m + + +# For use as a temporary user-defined object, to maximize sensitivity of the tests below. +class ValueHolder: + def __init__(self, value): + self.value = value + + +def test_cast_from_pyobject_ptr(): + assert m.cast_from_pyobject_ptr() == 6758 + + +def test_cast_handle_to_pyobject_ptr(): + assert m.cast_handle_to_pyobject_ptr(ValueHolder(24)) == 76 + + +def test_cast_object_to_pyobject_ptr(): + assert m.cast_object_to_pyobject_ptr(ValueHolder(43)) == 257 + + +def test_cast_list_to_pyobject_ptr(): + assert m.cast_list_to_pyobject_ptr([1, 2, 3, 4, 5]) == 395 + + +def test_return_pyobject_ptr(): + assert m.return_pyobject_ptr() == 2314 + + +def test_pass_pyobject_ptr(): + assert m.pass_pyobject_ptr(ValueHolder(82)) == 118 + + +@pytest.mark.parametrize( + "call_callback", + [ + m.call_callback_with_object_return, + m.call_callback_with_pyobject_ptr_return, + ], +) +def test_call_callback_with_object_return(call_callback): + def cb(value): + if value < 0: + raise ValueError("Raised from cb") + return ValueHolder(1000 - value) + + assert call_callback(cb, 287).value == 713 + + with pytest.raises(ValueError, match="^Raised from cb$"): + call_callback(cb, -1) + + +def test_call_callback_with_pyobject_ptr_arg(): + def cb(obj): + return 300 - obj.value + + assert m.call_callback_with_pyobject_ptr_arg(cb, ValueHolder(39)) == 261 + + +@pytest.mark.parametrize("set_error", [True, False]) +def test_cast_to_python_nullptr(set_error): + expected = { + True: r"^Reflective of healthy error handling\.$", + False: ( + r"^Internal error: pybind11::error_already_set called " + r"while Python error indicator not set\.$" + ), + }[set_error] + with pytest.raises(RuntimeError, match=expected): + m.cast_to_pyobject_ptr_nullptr(set_error) + + +def test_cast_to_python_non_nullptr_with_error_set(): + with pytest.raises(SystemError) as excinfo: + m.cast_to_pyobject_ptr_non_nullptr_with_error_set() + assert str(excinfo.value) == "src != nullptr but PyErr_Occurred()" + assert str(excinfo.value.__cause__) == "Reflective of unhealthy error handling." + + +def test_pass_list_pyobject_ptr(): + acc = m.pass_list_pyobject_ptr([ValueHolder(842), ValueHolder(452)]) + assert acc == 842452 + + +def test_return_list_pyobject_ptr_take_ownership(): + vec_obj = m.return_list_pyobject_ptr_take_ownership(ValueHolder) + assert [e.value for e in vec_obj] == [93, 186] + + +def test_return_list_pyobject_ptr_reference(): + vec_obj = m.return_list_pyobject_ptr_reference(ValueHolder) + assert [e.value for e in vec_obj] == [93, 186] + # Commenting out the next `assert` will leak the Python references. + # An easy way to see evidence of the leaks: + # Insert `while True:` as the first line of this function and monitor the + # process RES (Resident Memory Size) with the Unix top command. + assert m.dec_ref_each_pyobject_ptr(vec_obj) == 2 + + +def test_type_caster_name_via_incompatible_function_arguments_type_error(): + with pytest.raises(TypeError, match=r"1\. \(arg0: object, arg1: int\) -> None"): + m.pass_pyobject_ptr_and_int(ValueHolder(101), ValueHolder(202)) diff --git a/pybind11/tests/test_unnamed_namespace_a.cpp b/pybind11/tests/test_unnamed_namespace_a.cpp new file mode 100644 index 000000000..2152e64bd --- /dev/null +++ b/pybind11/tests/test_unnamed_namespace_a.cpp @@ -0,0 +1,38 @@ +#include "pybind11_tests.h" + +namespace { +struct any_struct {}; +} // namespace + +TEST_SUBMODULE(unnamed_namespace_a, m) { + if (py::detail::get_type_info(typeid(any_struct)) == nullptr) { + py::class_(m, "unnamed_namespace_a_any_struct"); + } else { + m.attr("unnamed_namespace_a_any_struct") = py::none(); + } + m.attr("PYBIND11_INTERNALS_VERSION") = PYBIND11_INTERNALS_VERSION; + m.attr("defined_WIN32_or__WIN32") = +#if defined(WIN32) || defined(_WIN32) + true; +#else + false; +#endif + m.attr("defined___clang__") = +#if defined(__clang__) + true; +#else + false; +#endif + m.attr("defined__LIBCPP_VERSION") = +#if defined(_LIBCPP_VERSION) + true; +#else + false; +#endif + m.attr("defined___GLIBCXX__") = +#if defined(__GLIBCXX__) + true; +#else + false; +#endif +} diff --git a/pybind11/tests/test_unnamed_namespace_a.py b/pybind11/tests/test_unnamed_namespace_a.py new file mode 100644 index 000000000..9d9856c5a --- /dev/null +++ b/pybind11/tests/test_unnamed_namespace_a.py @@ -0,0 +1,34 @@ +import pytest + +from pybind11_tests import unnamed_namespace_a as m +from pybind11_tests import unnamed_namespace_b as mb + +XFAIL_CONDITION = ( + "(m.PYBIND11_INTERNALS_VERSION <= 4 and (m.defined___clang__ or not m.defined___GLIBCXX__))" + " or " + "(m.PYBIND11_INTERNALS_VERSION >= 5 and not m.defined_WIN32_or__WIN32" + " and " + "(m.defined___clang__ or m.defined__LIBCPP_VERSION))" +) +XFAIL_REASON = "Known issues: https://github.com/pybind/pybind11/pull/4319" + + +@pytest.mark.xfail(XFAIL_CONDITION, reason=XFAIL_REASON, strict=False) +@pytest.mark.parametrize( + "any_struct", [m.unnamed_namespace_a_any_struct, mb.unnamed_namespace_b_any_struct] +) +def test_have_class_any_struct(any_struct): + assert any_struct is not None + + +def test_have_at_least_one_class_any_struct(): + assert ( + m.unnamed_namespace_a_any_struct is not None + or mb.unnamed_namespace_b_any_struct is not None + ) + + +@pytest.mark.xfail(XFAIL_CONDITION, reason=XFAIL_REASON, strict=True) +def test_have_both_class_any_struct(): + assert m.unnamed_namespace_a_any_struct is not None + assert mb.unnamed_namespace_b_any_struct is not None diff --git a/pybind11/tests/test_unnamed_namespace_b.cpp b/pybind11/tests/test_unnamed_namespace_b.cpp new file mode 100644 index 000000000..f97757a7d --- /dev/null +++ b/pybind11/tests/test_unnamed_namespace_b.cpp @@ -0,0 +1,13 @@ +#include "pybind11_tests.h" + +namespace { +struct any_struct {}; +} // namespace + +TEST_SUBMODULE(unnamed_namespace_b, m) { + if (py::detail::get_type_info(typeid(any_struct)) == nullptr) { + py::class_(m, "unnamed_namespace_b_any_struct"); + } else { + m.attr("unnamed_namespace_b_any_struct") = py::none(); + } +} diff --git a/pybind11/tests/test_unnamed_namespace_b.py b/pybind11/tests/test_unnamed_namespace_b.py new file mode 100644 index 000000000..4bcaa7a6c --- /dev/null +++ b/pybind11/tests/test_unnamed_namespace_b.py @@ -0,0 +1,5 @@ +from pybind11_tests import unnamed_namespace_b as m + + +def test_have_attr_any_struct(): + assert hasattr(m, "unnamed_namespace_b_any_struct") diff --git a/pybind11/tests/test_vector_unique_ptr_member.cpp b/pybind11/tests/test_vector_unique_ptr_member.cpp new file mode 100644 index 000000000..680cf9a6d --- /dev/null +++ b/pybind11/tests/test_vector_unique_ptr_member.cpp @@ -0,0 +1,54 @@ +#include "pybind11_tests.h" + +#include +#include +#include + +namespace pybind11_tests { +namespace vector_unique_ptr_member { + +struct DataType {}; + +// Reduced from a use case in the wild. +struct VectorOwner { + static std::unique_ptr Create(std::size_t num_elems) { + return std::unique_ptr( + new VectorOwner(std::vector>(num_elems))); + } + + std::size_t data_size() const { return data_.size(); } + +private: + explicit VectorOwner(std::vector> data) : data_(std::move(data)) {} + + const std::vector> data_; +}; + +} // namespace vector_unique_ptr_member +} // namespace pybind11_tests + +namespace pybind11 { +namespace detail { + +template <> +struct is_copy_constructible + : std::false_type {}; + +template <> +struct is_move_constructible + : std::false_type {}; + +} // namespace detail +} // namespace pybind11 + +using namespace pybind11_tests::vector_unique_ptr_member; + +py::object py_cast_VectorOwner_ptr(VectorOwner *ptr) { return py::cast(ptr); } + +TEST_SUBMODULE(vector_unique_ptr_member, m) { + py::class_(m, "VectorOwner") + .def_static("Create", &VectorOwner::Create) + .def("data_size", &VectorOwner::data_size); + + m.def("py_cast_VectorOwner_ptr", py_cast_VectorOwner_ptr); +} diff --git a/pybind11/tests/test_vector_unique_ptr_member.py b/pybind11/tests/test_vector_unique_ptr_member.py new file mode 100644 index 000000000..2da3d97c3 --- /dev/null +++ b/pybind11/tests/test_vector_unique_ptr_member.py @@ -0,0 +1,14 @@ +import pytest + +from pybind11_tests import vector_unique_ptr_member as m + + +@pytest.mark.parametrize("num_elems", range(3)) +def test_create(num_elems): + vo = m.VectorOwner.Create(num_elems) + assert vo.data_size() == num_elems + + +def test_cast(): + vo = m.VectorOwner.Create(0) + assert m.py_cast_VectorOwner_ptr(vo) is vo diff --git a/pybind11/tests/test_virtual_functions.cpp b/pybind11/tests/test_virtual_functions.cpp index 323aa0d22..93b136ad3 100644 --- a/pybind11/tests/test_virtual_functions.cpp +++ b/pybind11/tests/test_virtual_functions.cpp @@ -173,7 +173,8 @@ struct AdderBase { using DataVisitor = std::function; virtual void - operator()(const Data &first, const Data &second, const DataVisitor &visitor) const = 0; + operator()(const Data &first, const Data &second, const DataVisitor &visitor) const + = 0; virtual ~AdderBase() = default; AdderBase() = default; AdderBase(const AdderBase &) = delete; diff --git a/pybind11/tests/test_virtual_functions.py b/pybind11/tests/test_virtual_functions.py index 4d00d3690..c17af7df5 100644 --- a/pybind11/tests/test_virtual_functions.py +++ b/pybind11/tests/test_virtual_functions.py @@ -192,8 +192,7 @@ def test_move_support(): class NCVirtExt(m.NCVirt): def get_noncopyable(self, a, b): # Constructs and returns a new instance: - nc = m.NonCopyable(a * a, b * b) - return nc + return m.NonCopyable(a * a, b * b) def get_movable(self, a, b): # Return a referenced copy @@ -256,7 +255,7 @@ def test_dispatch_issue(msg): assert m.dispatch_issue_go(b) == "Yay.." -def test_recursive_dispatch_issue(msg): +def test_recursive_dispatch_issue(): """#3357: Recursive dispatch fails to find python function override""" class Data(m.Data): @@ -269,7 +268,7 @@ def test_recursive_dispatch_issue(msg): # lambda is a workaround, which adds extra frame to the # current CPython thread. Removing lambda reveals the bug # [https://github.com/pybind/pybind11/issues/3357] - (lambda: visitor(Data(first.value + second.value)))() + (lambda: visitor(Data(first.value + second.value)))() # noqa: PLC3002 class StoreResultVisitor: def __init__(self): diff --git a/pybind11/tools/FindCatch.cmake b/pybind11/tools/FindCatch.cmake index 57bba58b3..5d3fcbfb1 100644 --- a/pybind11/tools/FindCatch.cmake +++ b/pybind11/tools/FindCatch.cmake @@ -36,10 +36,14 @@ endfunction() function(_download_catch version destination_dir) message(STATUS "Downloading catch v${version}...") set(url https://github.com/philsquared/Catch/releases/download/v${version}/catch.hpp) - file(DOWNLOAD ${url} "${destination_dir}/catch.hpp" STATUS status) + file( + DOWNLOAD ${url} "${destination_dir}/catch.hpp" + STATUS status + LOG log) list(GET status 0 error) if(error) - message(FATAL_ERROR "Could not download ${url}") + string(REPLACE "\n" "\n " log " ${log}") + message(FATAL_ERROR "Could not download URL:\n" " ${url}\n" "Log:\n" "${log}") endif() set(CATCH_INCLUDE_DIR "${destination_dir}" diff --git a/pybind11/tools/FindPythonLibsNew.cmake b/pybind11/tools/FindPythonLibsNew.cmake index a5a628fd7..ce558d4ec 100644 --- a/pybind11/tools/FindPythonLibsNew.cmake +++ b/pybind11/tools/FindPythonLibsNew.cmake @@ -151,9 +151,13 @@ if(NOT _PYTHON_SUCCESS MATCHES 0) return() endif() +option( + PYBIND11_PYTHONLIBS_OVERWRITE + "Overwrite cached values read from Python library (classic search). Turn off if cross-compiling and manually setting these values." + ON) # Can manually set values when cross-compiling macro(_PYBIND11_GET_IF_UNDEF lst index name) - if(NOT DEFINED "${name}") + if(PYBIND11_PYTHONLIBS_OVERWRITE OR NOT DEFINED "${name}") list(GET "${lst}" "${index}" "${name}") endif() endmacro() @@ -204,7 +208,9 @@ string(REGEX REPLACE "\\\\" "/" PYTHON_PREFIX "${PYTHON_PREFIX}") string(REGEX REPLACE "\\\\" "/" PYTHON_INCLUDE_DIR "${PYTHON_INCLUDE_DIR}") string(REGEX REPLACE "\\\\" "/" PYTHON_SITE_PACKAGES "${PYTHON_SITE_PACKAGES}") -if(CMAKE_HOST_WIN32) +if(DEFINED PYTHON_LIBRARY) + # Don't write to PYTHON_LIBRARY if it's already set +elseif(CMAKE_HOST_WIN32) set(PYTHON_LIBRARY "${PYTHON_PREFIX}/libs/python${PYTHON_LIBRARY_SUFFIX}.lib") # when run in a venv, PYTHON_PREFIX points to it. But the libraries remain in the @@ -270,7 +276,7 @@ if(NOT PYTHON_DEBUG_LIBRARY) endif() set(PYTHON_DEBUG_LIBRARIES "${PYTHON_DEBUG_LIBRARY}") -find_package_message(PYTHON "Found PythonLibs: ${PYTHON_LIBRARY}" +find_package_message(PYTHON "Found PythonLibs: ${PYTHON_LIBRARIES}" "${PYTHON_EXECUTABLE}${PYTHON_VERSION_STRING}") set(PYTHONLIBS_FOUND TRUE) diff --git a/pybind11/tools/JoinPaths.cmake b/pybind11/tools/JoinPaths.cmake new file mode 100644 index 000000000..c68d91b84 --- /dev/null +++ b/pybind11/tools/JoinPaths.cmake @@ -0,0 +1,23 @@ +# This module provides function for joining paths +# known from most languages +# +# SPDX-License-Identifier: (MIT OR CC0-1.0) +# Copyright 2020 Jan Tojnar +# https://github.com/jtojnar/cmake-snips +# +# Modelled after Python’s os.path.join +# https://docs.python.org/3.7/library/os.path.html#os.path.join +# Windows not supported +function(join_paths joined_path first_path_segment) + set(temp_path "${first_path_segment}") + foreach(current_segment IN LISTS ARGN) + if(NOT ("${current_segment}" STREQUAL "")) + if(IS_ABSOLUTE "${current_segment}") + set(temp_path "${current_segment}") + else() + set(temp_path "${temp_path}/${current_segment}") + endif() + endif() + endforeach() + set(${joined_path} "${temp_path}" PARENT_SCOPE) +endfunction() diff --git a/pybind11/tools/codespell_ignore_lines_from_errors.py b/pybind11/tools/codespell_ignore_lines_from_errors.py new file mode 100644 index 000000000..4ec9add12 --- /dev/null +++ b/pybind11/tools/codespell_ignore_lines_from_errors.py @@ -0,0 +1,39 @@ +"""Simple script for rebuilding .codespell-ignore-lines + +Usage: + +cat < /dev/null > .codespell-ignore-lines +pre-commit run --all-files codespell >& /tmp/codespell_errors.txt +python3 tools/codespell_ignore_lines_from_errors.py /tmp/codespell_errors.txt > .codespell-ignore-lines + +git diff to review changes, then commit, push. +""" + +import sys +from typing import List + + +def run(args: List[str]) -> None: + assert len(args) == 1, "codespell_errors.txt" + cache = {} + done = set() + with open(args[0]) as f: + lines = f.read().splitlines() + + for line in sorted(lines): + i = line.find(" ==> ") + if i > 0: + flds = line[:i].split(":") + if len(flds) >= 2: + filename, line_num = flds[:2] + if filename not in cache: + with open(filename) as f: + cache[filename] = f.read().splitlines() + supp = cache[filename][int(line_num) - 1] + if supp not in done: + print(supp) + done.add(supp) + + +if __name__ == "__main__": + run(args=sys.argv[1:]) diff --git a/pybind11/tools/make_changelog.py b/pybind11/tools/make_changelog.py index 839040a93..b5bd83294 100755 --- a/pybind11/tools/make_changelog.py +++ b/pybind11/tools/make_changelog.py @@ -31,8 +31,10 @@ issues = (issue for page in issues_pages for issue in page) missing = [] for issue in issues: - changelog = ENTRY.findall(issue.body) - if changelog: + changelog = ENTRY.findall(issue.body or "") + if not changelog or not changelog[0]: + missing.append(issue) + else: (msg,) = changelog if not msg.startswith("* "): msg = "* " + msg @@ -44,9 +46,6 @@ for issue in issues: print(Syntax(msg, "rst", theme="ansi_light", word_wrap=True)) print() - else: - missing.append(issue) - if missing: print() print("[blue]" + "-" * 30) diff --git a/pybind11/tools/pybind11.pc.in b/pybind11/tools/pybind11.pc.in new file mode 100644 index 000000000..402f0b357 --- /dev/null +++ b/pybind11/tools/pybind11.pc.in @@ -0,0 +1,7 @@ +prefix=@prefix_for_pc_file@ +includedir=@includedir_for_pc_file@ + +Name: @PROJECT_NAME@ +Description: Seamless operability between C++11 and Python +Version: @PROJECT_VERSION@ +Cflags: -I${includedir} diff --git a/pybind11/tools/pybind11Common.cmake b/pybind11/tools/pybind11Common.cmake index e1fb601ac..308d1b70d 100644 --- a/pybind11/tools/pybind11Common.cmake +++ b/pybind11/tools/pybind11Common.cmake @@ -5,8 +5,8 @@ Adds the following targets:: pybind11::pybind11 - link to headers and pybind11 pybind11::module - Adds module links pybind11::embed - Adds embed links - pybind11::lto - Link time optimizations (manual selection) - pybind11::thin_lto - Link time optimizations (manual selection) + pybind11::lto - Link time optimizations (only if CMAKE_INTERPROCEDURAL_OPTIMIZATION is not set) + pybind11::thin_lto - Link time optimizations (only if CMAKE_INTERPROCEDURAL_OPTIMIZATION is not set) pybind11::python_link_helper - Adds link to Python libraries pybind11::windows_extras - MSVC bigobj and mp for building multithreaded pybind11::opt_size - avoid optimizations that increase code size @@ -20,7 +20,7 @@ Adds the following functions:: # CMake 3.10 has an include_guard command, but we can't use that yet # include_guard(global) (pre-CMake 3.10) -if(TARGET pybind11::lto) +if(TARGET pybind11::pybind11) return() endif() @@ -163,11 +163,19 @@ endif() # --------------------- Python specifics ------------------------- +# CMake 3.27 removes the classic FindPythonInterp if CMP0148 is NEW +if(CMAKE_VERSION VERSION_LESS "3.27") + set(_pybind11_missing_old_python "OLD") +else() + cmake_policy(GET CMP0148 _pybind11_missing_old_python) +endif() + # Check to see which Python mode we are in, new, old, or no python if(PYBIND11_NOPYTHON) set(_pybind11_nopython ON) elseif( - PYBIND11_FINDPYTHON + _pybind11_missing_old_python STREQUAL "NEW" + OR PYBIND11_FINDPYTHON OR Python_FOUND OR Python2_FOUND OR Python3_FOUND) @@ -311,6 +319,16 @@ function(_pybind11_generate_lto target prefer_thin_lto) HAS_FLTO "-flto${cxx_append}" "-flto${linker_append}" PYBIND11_LTO_CXX_FLAGS PYBIND11_LTO_LINKER_FLAGS) endif() + elseif(CMAKE_CXX_COMPILER_ID MATCHES "IntelLLVM") + # IntelLLVM equivalent to LTO is called IPO; also IntelLLVM is WIN32/UNIX + # WARNING/HELP WANTED: This block of code is currently not covered by pybind11 GitHub Actions! + if(WIN32) + _pybind11_return_if_cxx_and_linker_flags_work( + HAS_INTEL_IPO "-Qipo" "-Qipo" PYBIND11_LTO_CXX_FLAGS PYBIND11_LTO_LINKER_FLAGS) + else() + _pybind11_return_if_cxx_and_linker_flags_work( + HAS_INTEL_IPO "-ipo" "-ipo" PYBIND11_LTO_CXX_FLAGS PYBIND11_LTO_LINKER_FLAGS) + endif() elseif(CMAKE_CXX_COMPILER_ID MATCHES "Intel") # Intel equivalent to LTO is called IPO _pybind11_return_if_cxx_and_linker_flags_work(HAS_INTEL_IPO "-ipo" "-ipo" @@ -362,11 +380,13 @@ function(_pybind11_generate_lto target prefer_thin_lto) endif() endfunction() -add_library(pybind11::lto IMPORTED INTERFACE ${optional_global}) -_pybind11_generate_lto(pybind11::lto FALSE) +if(NOT DEFINED CMAKE_INTERPROCEDURAL_OPTIMIZATION) + add_library(pybind11::lto IMPORTED INTERFACE ${optional_global}) + _pybind11_generate_lto(pybind11::lto FALSE) -add_library(pybind11::thin_lto IMPORTED INTERFACE ${optional_global}) -_pybind11_generate_lto(pybind11::thin_lto TRUE) + add_library(pybind11::thin_lto IMPORTED INTERFACE ${optional_global}) + _pybind11_generate_lto(pybind11::thin_lto TRUE) +endif() # ---------------------- pybind11_strip ----------------------------- diff --git a/pybind11/tools/pybind11Config.cmake.in b/pybind11/tools/pybind11Config.cmake.in index 9383e8c67..5734f437b 100644 --- a/pybind11/tools/pybind11Config.cmake.in +++ b/pybind11/tools/pybind11Config.cmake.in @@ -63,7 +63,9 @@ Modes There are two modes provided; classic, which is built on the old Python discovery packages in CMake, or the new FindPython mode, which uses FindPython -from 3.12+ forward (3.15+ _highly_ recommended). +from 3.12+ forward (3.15+ _highly_ recommended). If you set the minimum or +maximum version of CMake to 3.27+, then FindPython is the default (since +FindPythonInterp/FindPythonLibs has been removed via policy `CMP0148`). New FindPython mode ^^^^^^^^^^^^^^^^^^^ diff --git a/pybind11/tools/pybind11NewTools.cmake b/pybind11/tools/pybind11NewTools.cmake index abba0fe0e..7d7424a79 100644 --- a/pybind11/tools/pybind11NewTools.cmake +++ b/pybind11/tools/pybind11NewTools.cmake @@ -9,7 +9,7 @@ if(CMAKE_VERSION VERSION_LESS 3.12) message(FATAL_ERROR "You cannot use the new FindPython module with CMake < 3.12") endif() -include_guard(GLOBAL) +include_guard(DIRECTORY) get_property( is_config @@ -233,7 +233,9 @@ function(pybind11_add_module target_name) endif() endif() - if(NOT MSVC AND NOT ${CMAKE_BUILD_TYPE} MATCHES Debug|RelWithDebInfo) + # Use case-insensitive comparison to match the result of $ + string(TOUPPER "${CMAKE_BUILD_TYPE}" uppercase_CMAKE_BUILD_TYPE) + if(NOT MSVC AND NOT "${uppercase_CMAKE_BUILD_TYPE}" MATCHES DEBUG|RELWITHDEBINFO) # Strip unnecessary sections of the binary on Linux/macOS pybind11_strip(${target_name}) endif() diff --git a/pybind11/tools/pybind11Tools.cmake b/pybind11/tools/pybind11Tools.cmake index 5535e872f..66ad00a47 100644 --- a/pybind11/tools/pybind11Tools.cmake +++ b/pybind11/tools/pybind11Tools.cmake @@ -115,6 +115,7 @@ if(PYTHON_IS_DEBUG) PROPERTY INTERFACE_COMPILE_DEFINITIONS Py_DEBUG) endif() +# The <3.11 code here does not support release/debug builds at the same time, like on vcpkg if(CMAKE_VERSION VERSION_LESS 3.11) set_property( TARGET pybind11::module @@ -130,16 +131,19 @@ if(CMAKE_VERSION VERSION_LESS 3.11) APPEND PROPERTY INTERFACE_LINK_LIBRARIES pybind11::pybind11 $) else() + # The IMPORTED INTERFACE library here is to ensure that "debug" and "release" get processed outside + # of a generator expression - https://gitlab.kitware.com/cmake/cmake/-/issues/18424, as they are + # target_link_library keywords rather than real libraries. + add_library(pybind11::_ClassicPythonLibraries IMPORTED INTERFACE) + target_link_libraries(pybind11::_ClassicPythonLibraries INTERFACE ${PYTHON_LIBRARIES}) target_link_libraries( pybind11::module INTERFACE pybind11::python_link_helper - "$<$,$>:$>" - ) + "$<$,$>:pybind11::_ClassicPythonLibraries>") target_link_libraries(pybind11::embed INTERFACE pybind11::pybind11 - $) - + pybind11::_ClassicPythonLibraries) endif() function(pybind11_extension name) @@ -208,7 +212,9 @@ function(pybind11_add_module target_name) endif() endif() - if(NOT MSVC AND NOT ${CMAKE_BUILD_TYPE} MATCHES Debug|RelWithDebInfo) + # Use case-insensitive comparison to match the result of $ + string(TOUPPER "${CMAKE_BUILD_TYPE}" uppercase_CMAKE_BUILD_TYPE) + if(NOT MSVC AND NOT "${uppercase_CMAKE_BUILD_TYPE}" MATCHES DEBUG|RELWITHDEBINFO) pybind11_strip(${target_name}) endif() diff --git a/pybind11/tools/setup_global.py.in b/pybind11/tools/setup_global.py.in index 8aa387178..885ac5c72 100644 --- a/pybind11/tools/setup_global.py.in +++ b/pybind11/tools/setup_global.py.in @@ -27,9 +27,11 @@ class InstallHeadersNested(install_headers): main_headers = glob.glob("pybind11/include/pybind11/*.h") detail_headers = glob.glob("pybind11/include/pybind11/detail/*.h") +eigen_headers = glob.glob("pybind11/include/pybind11/eigen/*.h") stl_headers = glob.glob("pybind11/include/pybind11/stl/*.h") cmake_files = glob.glob("pybind11/share/cmake/pybind11/*.cmake") -headers = main_headers + detail_headers + stl_headers +pkgconfig_files = glob.glob("pybind11/share/pkgconfig/*.pc") +headers = main_headers + detail_headers + stl_headers + eigen_headers cmdclass = {"install_headers": InstallHeadersNested} $extra_cmd @@ -51,8 +53,10 @@ setup( headers=headers, data_files=[ (base + "share/cmake/pybind11", cmake_files), + (base + "share/pkgconfig", pkgconfig_files), (base + "include/pybind11", main_headers), (base + "include/pybind11/detail", detail_headers), + (base + "include/pybind11/eigen", eigen_headers), (base + "include/pybind11/stl", stl_headers), ], cmdclass=cmdclass, diff --git a/pybind11/tools/setup_main.py.in b/pybind11/tools/setup_main.py.in index 738d73faa..6358cc7b9 100644 --- a/pybind11/tools/setup_main.py.in +++ b/pybind11/tools/setup_main.py.in @@ -15,15 +15,19 @@ setup( "pybind11", "pybind11.include.pybind11", "pybind11.include.pybind11.detail", + "pybind11.include.pybind11.eigen", "pybind11.include.pybind11.stl", "pybind11.share.cmake.pybind11", + "pybind11.share.pkgconfig", ], package_data={ "pybind11": ["py.typed"], "pybind11.include.pybind11": ["*.h"], "pybind11.include.pybind11.detail": ["*.h"], + "pybind11.include.pybind11.eigen": ["*.h"], "pybind11.include.pybind11.stl": ["*.h"], "pybind11.share.cmake.pybind11": ["*.cmake"], + "pybind11.share.pkgconfig": ["*.pc"], }, extras_require={ "global": ["pybind11_global==$version"] diff --git a/tests/expected/matlab/TemplatedFunctionRot3.m b/tests/expected/matlab/TemplatedFunctionRot3.m index 13f661342..07dbd6217 100644 --- a/tests/expected/matlab/TemplatedFunctionRot3.m +++ b/tests/expected/matlab/TemplatedFunctionRot3.m @@ -1,6 +1,6 @@ function varargout = TemplatedFunctionRot3(varargin) if length(varargin) == 1 && isa(varargin{1},'gtsam.Rot3') - functions_wrapper(25, varargin{:}); + functions_wrapper(26, varargin{:}); else error('Arguments do not match any overload of function TemplatedFunctionRot3'); end diff --git a/tests/expected/matlab/functions_wrapper.cpp b/tests/expected/matlab/functions_wrapper.cpp index 7c6a70e4a..2e5212086 100644 --- a/tests/expected/matlab/functions_wrapper.cpp +++ b/tests/expected/matlab/functions_wrapper.cpp @@ -229,7 +229,16 @@ void setPose_24(int nargout, mxArray *out[], int nargin, const mxArray *in[]) checkArguments("setPose",nargout,nargin,0); setPose(gtsam::Pose3()); } -void TemplatedFunctionRot3_25(int nargout, mxArray *out[], int nargin, const mxArray *in[]) +void EliminateDiscrete_25(int nargout, mxArray *out[], int nargin, const mxArray *in[]) +{ + checkArguments("EliminateDiscrete",nargout,nargin,2); + gtsam::DiscreteFactorGraph& factors = *unwrap_shared_ptr< gtsam::DiscreteFactorGraph >(in[0], "ptr_gtsamDiscreteFactorGraph"); + gtsam::Ordering& frontalKeys = *unwrap_shared_ptr< gtsam::Ordering >(in[1], "ptr_gtsamOrdering"); + auto pairResult = EliminateDiscrete(factors,frontalKeys); + out[0] = wrap_shared_ptr(pairResult.first,"gtsam.DiscreteConditional", false); + out[1] = wrap_shared_ptr(pairResult.second,"gtsam.DecisionTreeFactor", false); +} +void TemplatedFunctionRot3_26(int nargout, mxArray *out[], int nargin, const mxArray *in[]) { checkArguments("TemplatedFunctionRot3",nargout,nargin,1); gtsam::Rot3& t = *unwrap_shared_ptr< gtsam::Rot3 >(in[0], "ptr_gtsamRot3"); @@ -323,7 +332,10 @@ void mexFunction(int nargout, mxArray *out[], int nargin, const mxArray *in[]) setPose_24(nargout, out, nargin-1, in+1); break; case 25: - TemplatedFunctionRot3_25(nargout, out, nargin-1, in+1); + EliminateDiscrete_25(nargout, out, nargin-1, in+1); + break; + case 26: + TemplatedFunctionRot3_26(nargout, out, nargin-1, in+1); break; } } catch(const std::exception& e) { diff --git a/tests/expected/python/functions_pybind.cpp b/tests/expected/python/functions_pybind.cpp index b0490dcdc..dcbd9eed5 100644 --- a/tests/expected/python/functions_pybind.cpp +++ b/tests/expected/python/functions_pybind.cpp @@ -30,6 +30,7 @@ PYBIND11_MODULE(functions_py, m_) { m_.def("DefaultFuncZero",[](int a, int b, double c, int d, bool e){ ::DefaultFuncZero(a, b, c, d, e);}, py::arg("a"), py::arg("b"), py::arg("c") = 0.0, py::arg("d") = 0, py::arg("e") = false); m_.def("DefaultFuncVector",[](const std::vector& i, const std::vector& s){ ::DefaultFuncVector(i, s);}, py::arg("i") = {1, 2, 3}, py::arg("s") = {"borglab", "gtsam"}); m_.def("setPose",[](const gtsam::Pose3& pose){ ::setPose(pose);}, py::arg("pose") = gtsam::Pose3()); + m_.def("EliminateDiscrete",[](const gtsam::DiscreteFactorGraph& factors, const gtsam::Ordering& frontalKeys){return ::EliminateDiscrete(factors, frontalKeys);}, py::arg("factors"), py::arg("frontalKeys")); m_.def("TemplatedFunctionRot3",[](const gtsam::Rot3& t){ ::TemplatedFunction(t);}, py::arg("t")); #include "python/specializations.h" diff --git a/tests/fixtures/functions.i b/tests/fixtures/functions.i index 7f3c83332..22babd6d9 100644 --- a/tests/fixtures/functions.i +++ b/tests/fixtures/functions.i @@ -36,3 +36,7 @@ void DefaultFuncVector(const std::vector &i = {1, 2, 3}, const std::vector< // Test for non-trivial default constructor void setPose(const gtsam::Pose3& pose = gtsam::Pose3()); + +std::pair +EliminateDiscrete(const gtsam::DiscreteFactorGraph& factors, + const gtsam::Ordering& frontalKeys); \ No newline at end of file From 790e3d515c16fcc7d45d28961278862b0cd93e16 Mon Sep 17 00:00:00 2001 From: Varun Agrawal Date: Fri, 6 Oct 2023 12:34:49 -0400 Subject: [PATCH 090/113] add templated at methods for FactorGraph so it can perform typecasting for us --- .../tests/testHybridGaussianFactorGraph.cpp | 6 +++--- gtsam/inference/FactorGraph.h | 15 +++++++++++++++ gtsam/linear/tests/testGaussianFactorGraph.cpp | 3 +-- gtsam/nonlinear/GncOptimizer.h | 15 ++++++--------- gtsam/sfm/tests/testShonanAveraging.cpp | 8 +++++--- gtsam/slam/tests/testDataset.cpp | 13 +++++-------- gtsam_unstable/discrete/tests/testLoopyBelief.cpp | 7 ++----- .../nonlinear/tests/testNonlinearClusterTree.cpp | 2 +- tests/testNonlinearFactor.cpp | 2 +- 9 files changed, 39 insertions(+), 32 deletions(-) diff --git a/gtsam/hybrid/tests/testHybridGaussianFactorGraph.cpp b/gtsam/hybrid/tests/testHybridGaussianFactorGraph.cpp index df38c171e..b240e1626 100644 --- a/gtsam/hybrid/tests/testHybridGaussianFactorGraph.cpp +++ b/gtsam/hybrid/tests/testHybridGaussianFactorGraph.cpp @@ -42,8 +42,8 @@ #include #include #include -#include #include +#include #include "Switching.h" #include "TinyHybridExample.h" @@ -613,11 +613,11 @@ TEST(HybridGaussianFactorGraph, assembleGraphTree) { // Create expected decision tree with two factor graphs: // Get mixture factor: - auto mixture = std::dynamic_pointer_cast(fg.at(0)); + auto mixture = fg.at(0); CHECK(mixture); // Get prior factor: - const auto gf = std::dynamic_pointer_cast(fg.at(1)); + const auto gf = fg.at(1); CHECK(gf); using GF = GaussianFactor::shared_ptr; const GF prior = gf->asGaussian(); diff --git a/gtsam/inference/FactorGraph.h b/gtsam/inference/FactorGraph.h index 327fca49a..b6046d0bb 100644 --- a/gtsam/inference/FactorGraph.h +++ b/gtsam/inference/FactorGraph.h @@ -310,6 +310,21 @@ class FactorGraph { */ sharedFactor& at(size_t i) { return factors_.at(i); } + /** Get a specific factor by index and typecast to factor type F + * (this checks array bounds and may throw + * an exception, as opposed to operator[] which does not). + */ + template + std::shared_ptr at(size_t i) { + return std::dynamic_pointer_cast(factors_.at(i)); + } + + /// Const version of templated `at` method. + template + const std::shared_ptr at(size_t i) const { + return std::dynamic_pointer_cast(factors_.at(i)); + } + /** Get a specific factor by index (this does not check array bounds, as * opposed to at() which does). */ diff --git a/gtsam/linear/tests/testGaussianFactorGraph.cpp b/gtsam/linear/tests/testGaussianFactorGraph.cpp index 2ba00abc1..03222bb3f 100644 --- a/gtsam/linear/tests/testGaussianFactorGraph.cpp +++ b/gtsam/linear/tests/testGaussianFactorGraph.cpp @@ -391,8 +391,7 @@ TEST(GaussianFactorGraph, clone) { EXPECT(assert_equal(init_graph, actCloned)); // Same as the original version // Apply an in-place change to init_graph and compare - JacobianFactor::shared_ptr jacFactor0 = - std::dynamic_pointer_cast(init_graph.at(0)); + JacobianFactor::shared_ptr jacFactor0 = init_graph.at(0); CHECK(jacFactor0); jacFactor0->getA(jacFactor0->begin()) *= 7.; EXPECT(assert_inequal(init_graph, exp_graph)); diff --git a/gtsam/nonlinear/GncOptimizer.h b/gtsam/nonlinear/GncOptimizer.h index d59eb4371..b646d009e 100644 --- a/gtsam/nonlinear/GncOptimizer.h +++ b/gtsam/nonlinear/GncOptimizer.h @@ -65,10 +65,9 @@ class GncOptimizer { nfg_.resize(graph.size()); for (size_t i = 0; i < graph.size(); i++) { if (graph[i]) { - NoiseModelFactor::shared_ptr factor = std::dynamic_pointer_cast< - NoiseModelFactor>(graph[i]); - auto robust = std::dynamic_pointer_cast< - noiseModel::Robust>(factor->noiseModel()); + NoiseModelFactor::shared_ptr factor = graph.at(i); + auto robust = + std::dynamic_pointer_cast(factor->noiseModel()); // if the factor has a robust loss, we remove the robust loss nfg_[i] = robust ? factor-> cloneWithNewNoiseModel(robust->noise()) : factor; } @@ -401,11 +400,9 @@ class GncOptimizer { newGraph.resize(nfg_.size()); for (size_t i = 0; i < nfg_.size(); i++) { if (nfg_[i]) { - auto factor = std::dynamic_pointer_cast< - NoiseModelFactor>(nfg_[i]); - auto noiseModel = - std::dynamic_pointer_cast( - factor->noiseModel()); + auto factor = nfg_.at(i); + auto noiseModel = std::dynamic_pointer_cast( + factor->noiseModel()); if (noiseModel) { Matrix newInfo = weights[i] * noiseModel->information(); auto newNoiseModel = noiseModel::Gaussian::Information(newInfo); diff --git a/gtsam/sfm/tests/testShonanAveraging.cpp b/gtsam/sfm/tests/testShonanAveraging.cpp index dd4759daa..dfa725ab6 100644 --- a/gtsam/sfm/tests/testShonanAveraging.cpp +++ b/gtsam/sfm/tests/testShonanAveraging.cpp @@ -372,9 +372,11 @@ TEST(ShonanAveraging2, noisyToyGraphWithHuber) { // test that each factor is actually robust for (size_t i=0; i<=4; i++) { // note: last is the Gauge factor and is not robust - const auto &robust = std::dynamic_pointer_cast( - std::dynamic_pointer_cast(graph[i])->noiseModel()); - EXPECT(robust); // we expect the factors to be use a robust noise model (in particular, Huber) + const auto &robust = std::dynamic_pointer_cast( + graph.at(i)->noiseModel()); + // we expect the factors to be use a robust noise model + // (in particular, Huber) + EXPECT(robust); } // test result diff --git a/gtsam/slam/tests/testDataset.cpp b/gtsam/slam/tests/testDataset.cpp index 8591a3932..13104174f 100644 --- a/gtsam/slam/tests/testDataset.cpp +++ b/gtsam/slam/tests/testDataset.cpp @@ -97,8 +97,7 @@ TEST(dataSet, load2D) { auto model = noiseModel::Unit::Create(3); BetweenFactor expected(1, 0, Pose2(-0.99879, 0.0417574, -0.00818381), model); - BetweenFactor::shared_ptr actual = - std::dynamic_pointer_cast>(graph->at(0)); + BetweenFactor::shared_ptr actual = graph->at>(0); EXPECT(assert_equal(expected, *actual)); // Check binary measurements, Pose2 @@ -113,9 +112,8 @@ TEST(dataSet, load2D) { // // Check factor parsing const auto actualFactors = parseFactors(filename); for (size_t i : {0, 1, 2, 3, 4, 5}) { - EXPECT(assert_equal( - *std::dynamic_pointer_cast>(graph->at(i)), - *actualFactors[i], 1e-5)); + EXPECT(assert_equal(*graph->at>(i), *actualFactors[i], + 1e-5)); } // Check pose parsing @@ -194,9 +192,8 @@ TEST(dataSet, readG2o3D) { // Check factor parsing const auto actualFactors = parseFactors(g2oFile); for (size_t i : {0, 1, 2, 3, 4, 5}) { - EXPECT(assert_equal( - *std::dynamic_pointer_cast>(expectedGraph[i]), - *actualFactors[i], 1e-5)); + EXPECT(assert_equal(*expectedGraph.at>(i), + *actualFactors[i], 1e-5)); } // Check pose parsing diff --git a/gtsam_unstable/discrete/tests/testLoopyBelief.cpp b/gtsam_unstable/discrete/tests/testLoopyBelief.cpp index 7c769fd78..39d9d743b 100644 --- a/gtsam_unstable/discrete/tests/testLoopyBelief.cpp +++ b/gtsam_unstable/discrete/tests/testLoopyBelief.cpp @@ -183,13 +183,10 @@ class LoopyBelief { // accumulate unary factors if (graph.at(factorIndex)->size() == 1) { if (!prodOfUnaries) - prodOfUnaries = std::dynamic_pointer_cast( - graph.at(factorIndex)); + prodOfUnaries = graph.at(factorIndex); else prodOfUnaries = std::make_shared( - *prodOfUnaries * - (*std::dynamic_pointer_cast( - graph.at(factorIndex)))); + *prodOfUnaries * (*graph.at(factorIndex))); } } diff --git a/gtsam_unstable/nonlinear/tests/testNonlinearClusterTree.cpp b/gtsam_unstable/nonlinear/tests/testNonlinearClusterTree.cpp index 38cfd7348..bbb461abb 100644 --- a/gtsam_unstable/nonlinear/tests/testNonlinearClusterTree.cpp +++ b/gtsam_unstable/nonlinear/tests/testNonlinearClusterTree.cpp @@ -87,7 +87,7 @@ TEST(NonlinearClusterTree, Clusters) { Ordering ordering; ordering.push_back(x1); const auto [bn, fg] = gfg->eliminatePartialSequential(ordering); - auto expectedFactor = std::dynamic_pointer_cast(fg->at(0)); + auto expectedFactor = fg->at(0); if (!expectedFactor) throw std::runtime_error("Expected HessianFactor"); diff --git a/tests/testNonlinearFactor.cpp b/tests/testNonlinearFactor.cpp index 06ae3366c..cca78b80e 100644 --- a/tests/testNonlinearFactor.cpp +++ b/tests/testNonlinearFactor.cpp @@ -323,7 +323,7 @@ TEST( NonlinearFactor, cloneWithNewNoiseModel ) // create actual NonlinearFactorGraph actual; SharedNoiseModel noise2 = noiseModel::Isotropic::Sigma(2,sigma2); - actual.push_back( std::dynamic_pointer_cast(nfg[0])->cloneWithNewNoiseModel(noise2) ); + actual.push_back(nfg.at(0)->cloneWithNewNoiseModel(noise2)); // check it's all good CHECK(assert_equal(expected, actual)); From 4750d850cef1f180c6bf143c6471d698b2f7ed16 Mon Sep 17 00:00:00 2001 From: ShuangLiu1992 Date: Sat, 7 Oct 2023 03:59:35 +0000 Subject: [PATCH 091/113] guard serialization.h --- gtsam/base/serialization.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/gtsam/base/serialization.h b/gtsam/base/serialization.h index e615afe83..12556db09 100644 --- a/gtsam/base/serialization.h +++ b/gtsam/base/serialization.h @@ -16,7 +16,7 @@ * @author Richard Roberts * @date Feb 7, 2012 */ - +#ifdef GTSAM_ENABLE_BOOST_SERIALIZATION #pragma once #include @@ -270,3 +270,4 @@ void deserializeBinary(const std::string& serialized, T& output, ///@} } // namespace gtsam +#endif \ No newline at end of file From 1d861d49d778cda4293d06ac707ba15ab54d0050 Mon Sep 17 00:00:00 2001 From: Varun Agrawal Date: Sat, 7 Oct 2023 16:11:50 -0400 Subject: [PATCH 092/113] remove automatic install of python dev dependencies, leave that to the user --- python/CMakeLists.txt | 6 ------ 1 file changed, 6 deletions(-) diff --git a/python/CMakeLists.txt b/python/CMakeLists.txt index 61c9797c2..f874c2f21 100644 --- a/python/CMakeLists.txt +++ b/python/CMakeLists.txt @@ -5,12 +5,6 @@ if (NOT GTSAM_BUILD_PYTHON) return() endif() -# Install development dependencies to build wrapper -message(STATUS "Installing Python development dependencies") -execute_process(COMMAND "${PYTHON_EXECUTABLE}" "-m" "pip" "install" "-r" "dev_requirements.txt" - WORKING_DIRECTORY ${PROJECT_PYTHON_SOURCE_DIR} - OUTPUT_QUIET) - # Generate setup.py. file(READ "${PROJECT_SOURCE_DIR}/README.md" README_CONTENTS) configure_file(${PROJECT_PYTHON_SOURCE_DIR}/setup.py.in From 3f3578ee6f9733b3819d223244307dfd4e6f49c5 Mon Sep 17 00:00:00 2001 From: Varun Agrawal Date: Sat, 7 Oct 2023 16:19:02 -0400 Subject: [PATCH 093/113] remove previous guard and do some formatting --- gtsam/base/serialization.h | 3 ++- gtsam/precompiled_header.h | 2 -- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/gtsam/base/serialization.h b/gtsam/base/serialization.h index 12556db09..18612bc22 100644 --- a/gtsam/base/serialization.h +++ b/gtsam/base/serialization.h @@ -16,6 +16,7 @@ * @author Richard Roberts * @date Feb 7, 2012 */ + #ifdef GTSAM_ENABLE_BOOST_SERIALIZATION #pragma once @@ -270,4 +271,4 @@ void deserializeBinary(const std::string& serialized, T& output, ///@} } // namespace gtsam -#endif \ No newline at end of file +#endif diff --git a/gtsam/precompiled_header.h b/gtsam/precompiled_header.h index e7188fc05..5ff2a55c5 100644 --- a/gtsam/precompiled_header.h +++ b/gtsam/precompiled_header.h @@ -42,10 +42,8 @@ #include #include #include -#ifdef GTSAM_ENABLE_BOOST_SERIALIZATION #include #include -#endif #include #include #include From 086d58e21e4b22bc175d27e74e814c72bce4a6d3 Mon Sep 17 00:00:00 2001 From: Varun Agrawal Date: Sat, 7 Oct 2023 16:25:11 -0400 Subject: [PATCH 094/113] install python dependencies in CI --- .github/workflows/build-python.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build-python.yml b/.github/workflows/build-python.yml index 480c791dc..cc282570d 100644 --- a/.github/workflows/build-python.yml +++ b/.github/workflows/build-python.yml @@ -109,10 +109,13 @@ jobs: with: swap-size-gb: 6 - - name: Install Dependencies + - name: Install System Dependencies run: | bash .github/scripts/python.sh -d + - name: Install Python Dependencies + run: pip install -r python/dev_requirements.txt + - name: Build run: | bash .github/scripts/python.sh -b From 06200f0dbd4599c19afdcc8800f2b359e7b49312 Mon Sep 17 00:00:00 2001 From: Varun Agrawal Date: Sat, 7 Oct 2023 17:26:40 -0400 Subject: [PATCH 095/113] correct way to install dependencies --- .github/workflows/build-python.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-python.yml b/.github/workflows/build-python.yml index cc282570d..4b31bf3e7 100644 --- a/.github/workflows/build-python.yml +++ b/.github/workflows/build-python.yml @@ -114,7 +114,7 @@ jobs: bash .github/scripts/python.sh -d - name: Install Python Dependencies - run: pip install -r python/dev_requirements.txt + run: $PYTHON -m pip install -r python/dev_requirements.txt - name: Build run: | From 3fac23b9b946ecfb2c94311239b55aa6a7886c88 Mon Sep 17 00:00:00 2001 From: Varun Agrawal Date: Sat, 7 Oct 2023 17:32:34 -0400 Subject: [PATCH 096/113] fix --- .github/workflows/build-python.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-python.yml b/.github/workflows/build-python.yml index 4b31bf3e7..91bc4e80a 100644 --- a/.github/workflows/build-python.yml +++ b/.github/workflows/build-python.yml @@ -114,7 +114,7 @@ jobs: bash .github/scripts/python.sh -d - name: Install Python Dependencies - run: $PYTHON -m pip install -r python/dev_requirements.txt + run: python$PYTHON_VERSION -m pip install -r python/dev_requirements.txt - name: Build run: | From 8f61d0b2edab0f65fa1c7fc21f4a4a44322bc82a Mon Sep 17 00:00:00 2001 From: Varun Agrawal Date: Sun, 8 Oct 2023 11:23:54 -0400 Subject: [PATCH 097/113] mark private options as advanced and move GTSAM specific options to HandleGeneralOptions.cmake --- cmake/GtsamBuildTypes.cmake | 18 ++++++++++++++---- cmake/GtsamTesting.cmake | 16 ++++------------ cmake/HandleGeneralOptions.cmake | 21 +++++++++++++++++++++ 3 files changed, 39 insertions(+), 16 deletions(-) diff --git a/cmake/GtsamBuildTypes.cmake b/cmake/GtsamBuildTypes.cmake index b24be5f08..2aad58abb 100644 --- a/cmake/GtsamBuildTypes.cmake +++ b/cmake/GtsamBuildTypes.cmake @@ -55,9 +55,6 @@ if(NOT CMAKE_BUILD_TYPE AND NOT MSVC AND NOT XCODE_VERSION) "Choose the type of build, options are: None Debug Release Timing Profiling RelWithDebInfo." FORCE) endif() -# Add option for using build type postfixes to allow installing multiple build modes -option(GTSAM_BUILD_TYPE_POSTFIXES "Enable/Disable appending the build type to the name of compiled libraries" ON) - # Define all cache variables, to be populated below depending on the OS/compiler: set(GTSAM_COMPILE_OPTIONS_PRIVATE "" CACHE INTERNAL "(Do not edit) Private compiler flags for all build configurations." FORCE) set(GTSAM_COMPILE_OPTIONS_PUBLIC "" CACHE INTERNAL "(Do not edit) Public compiler flags (exported to user projects) for all build configurations." FORCE) @@ -82,6 +79,13 @@ set(GTSAM_COMPILE_DEFINITIONS_PRIVATE_RELWITHDEBINFO "NDEBUG" CACHE STRING "(Us set(GTSAM_COMPILE_DEFINITIONS_PRIVATE_RELEASE "NDEBUG" CACHE STRING "(User editable) Private preprocessor macros for Release configuration.") set(GTSAM_COMPILE_DEFINITIONS_PRIVATE_PROFILING "NDEBUG" CACHE STRING "(User editable) Private preprocessor macros for Profiling configuration.") set(GTSAM_COMPILE_DEFINITIONS_PRIVATE_TIMING "NDEBUG;ENABLE_TIMING" CACHE STRING "(User editable) Private preprocessor macros for Timing configuration.") + +mark_as_advanced(GTSAM_COMPILE_DEFINITIONS_PRIVATE_DEBUG) +mark_as_advanced(GTSAM_COMPILE_DEFINITIONS_PRIVATE_RELWITHDEBINFO) +mark_as_advanced(GTSAM_COMPILE_DEFINITIONS_PRIVATE_RELEASE) +mark_as_advanced(GTSAM_COMPILE_DEFINITIONS_PRIVATE_PROFILING) +mark_as_advanced(GTSAM_COMPILE_DEFINITIONS_PRIVATE_TIMING) + if(MSVC) # Common to all configurations: list_append_cache(GTSAM_COMPILE_DEFINITIONS_PRIVATE @@ -143,6 +147,13 @@ else() set(GTSAM_COMPILE_OPTIONS_PRIVATE_TIMING -g -O3 CACHE STRING "(User editable) Private compiler flags for Timing configuration.") endif() +mark_as_advanced(GTSAM_COMPILE_OPTIONS_PRIVATE_COMMON) +mark_as_advanced(GTSAM_COMPILE_OPTIONS_PRIVATE_DEBUG) +mark_as_advanced(GTSAM_COMPILE_OPTIONS_PRIVATE_RELWITHDEBINFO) +mark_as_advanced(GTSAM_COMPILE_OPTIONS_PRIVATE_RELEASE) +mark_as_advanced(GTSAM_COMPILE_OPTIONS_PRIVATE_PROFILING) +mark_as_advanced(GTSAM_COMPILE_OPTIONS_PRIVATE_TIMING) + # Enable C++17: if (NOT CMAKE_VERSION VERSION_LESS 3.8) set(GTSAM_COMPILE_FEATURES_PUBLIC "cxx_std_17" CACHE STRING "CMake compile features property for all gtsam targets.") @@ -198,7 +209,6 @@ if(${CMAKE_CXX_COMPILER_ID} STREQUAL "Clang") endif() if (NOT MSVC) - option(GTSAM_BUILD_WITH_MARCH_NATIVE "Enable/Disable building with all instructions supported by native architecture (binary may not be portable!)" OFF) if(GTSAM_BUILD_WITH_MARCH_NATIVE) # Check if Apple OS and compiler is [Apple]Clang if(APPLE AND (${CMAKE_CXX_COMPILER_ID} MATCHES "^(Apple)?Clang$")) diff --git a/cmake/GtsamTesting.cmake b/cmake/GtsamTesting.cmake index 573fb696a..cea83a1dc 100644 --- a/cmake/GtsamTesting.cmake +++ b/cmake/GtsamTesting.cmake @@ -52,6 +52,10 @@ endmacro() # an empty string "" if nothing needs to be excluded. # linkLibraries: The list of libraries to link to. macro(gtsamAddExamplesGlob globPatterns excludedFiles linkLibraries) + if(NOT DEFINED GTSAM_BUILD_EXAMPLES_ALWAYS) + set(GTSAM_BUILD_EXAMPLES_ALWAYS OFF) + endif() + gtsamAddExesGlob_impl("${globPatterns}" "${excludedFiles}" "${linkLibraries}" "examples" ${GTSAM_BUILD_EXAMPLES_ALWAYS}) endmacro() @@ -86,18 +90,6 @@ endmacro() # Build macros for using tests enable_testing() -option(GTSAM_BUILD_TESTS "Enable/Disable building of tests" ON) -option(GTSAM_BUILD_EXAMPLES_ALWAYS "Build examples with 'make all' (build with 'make examples' if not)" ON) -option(GTSAM_BUILD_TIMING_ALWAYS "Build timing scripts with 'make all' (build with 'make timing' if not" OFF) - -# Add option for combining unit tests -if(MSVC OR XCODE_VERSION) - option(GTSAM_SINGLE_TEST_EXE "Combine unit tests into single executable (faster compile)" ON) -else() - option(GTSAM_SINGLE_TEST_EXE "Combine unit tests into single executable (faster compile)" OFF) -endif() -mark_as_advanced(GTSAM_SINGLE_TEST_EXE) - # Enable make check (http://www.cmake.org/Wiki/CMakeEmulateMakeCheck) if(GTSAM_BUILD_TESTS) add_custom_target(check COMMAND ${CMAKE_CTEST_COMMAND} -C $ --output-on-failure) diff --git a/cmake/HandleGeneralOptions.cmake b/cmake/HandleGeneralOptions.cmake index 13000a5b0..47a3a597d 100644 --- a/cmake/HandleGeneralOptions.cmake +++ b/cmake/HandleGeneralOptions.cmake @@ -8,6 +8,27 @@ else() set(GTSAM_UNSTABLE_AVAILABLE 0) endif() +### GtsamTesting related options +option(GTSAM_BUILD_TESTS "Enable/Disable building of tests" ON) +option(GTSAM_BUILD_EXAMPLES_ALWAYS "Build examples with 'make all' (build with 'make examples' if not)" ON) +option(GTSAM_BUILD_TIMING_ALWAYS "Build timing scripts with 'make all' (build with 'make timing' if not" OFF) + +# Add option for combining unit tests +if(MSVC OR XCODE_VERSION) + option(GTSAM_SINGLE_TEST_EXE "Combine unit tests into single executable (faster compile)" ON) +else() + option(GTSAM_SINGLE_TEST_EXE "Combine unit tests into single executable (faster compile)" OFF) +endif() +mark_as_advanced(GTSAM_SINGLE_TEST_EXE) +### + +# Add option for using build type postfixes to allow installing multiple build modes +option(GTSAM_BUILD_TYPE_POSTFIXES "Enable/Disable appending the build type to the name of compiled libraries" ON) + +if (NOT MSVC) + option(GTSAM_BUILD_WITH_MARCH_NATIVE "Enable/Disable building with all instructions supported by native architecture (binary may not be portable!)" OFF) +endif() + # Configurable Options if(GTSAM_UNSTABLE_AVAILABLE) option(GTSAM_BUILD_UNSTABLE "Enable/Disable libgtsam_unstable" ON) From f47006d187534f007eaa2622f173a3c56f66cf3d Mon Sep 17 00:00:00 2001 From: Varun Agrawal Date: Sun, 8 Oct 2023 11:24:26 -0400 Subject: [PATCH 098/113] include cmake files after options have been handled --- CMakeLists.txt | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 309cc7c4e..044f09e02 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -42,13 +42,6 @@ set(CMAKE_MODULE_PATH "${CMAKE_MODULE_PATH}" "${CMAKE_CURRENT_SOURCE_DIR}/cmake" include(GtsamMakeConfigFile) include(GNUInstallDirs) -# Load build type flags and default to Debug mode -include(GtsamBuildTypes) - -# Use macros for creating tests/timing scripts -include(GtsamTesting) -include(GtsamPrinting) - # guard against in-source builds if(${GTSAM_SOURCE_DIR} STREQUAL ${GTSAM_BINARY_DIR}) message(FATAL_ERROR "In-source builds not allowed. Please make a new directory (called a build directory) and run CMake from there. You may need to remove CMakeCache.txt. ") @@ -56,6 +49,13 @@ endif() include(cmake/HandleGeneralOptions.cmake) # CMake build options +# Load build type flags and default to Debug mode +include(GtsamBuildTypes) + +# Use macros for creating tests/timing scripts +include(GtsamTesting) +include(GtsamPrinting) + ############### Decide on BOOST ###################################### # Enable or disable serialization with GTSAM_ENABLE_BOOST_SERIALIZATION option(GTSAM_ENABLE_BOOST_SERIALIZATION "Enable Boost serialization" ON) From e4fff746900b8eff5a603eeaf5008d02bd70c4a1 Mon Sep 17 00:00:00 2001 From: Varun Agrawal Date: Sun, 8 Oct 2023 12:16:24 -0400 Subject: [PATCH 099/113] update gtsamAddExamplesGlob and gtsamAddTimingGlob to take an additional argument rather than using a global variable --- cmake/GtsamTesting.cmake | 16 +++++++--------- cmake/README.html | 5 +++-- cmake/README.md | 5 +++-- examples/CMakeLists.txt | 2 +- gtsam_unstable/discrete/examples/CMakeLists.txt | 2 +- gtsam_unstable/examples/CMakeLists.txt | 2 +- gtsam_unstable/timing/CMakeLists.txt | 2 +- timing/CMakeLists.txt | 2 +- 8 files changed, 18 insertions(+), 18 deletions(-) diff --git a/cmake/GtsamTesting.cmake b/cmake/GtsamTesting.cmake index cea83a1dc..c604b18ab 100644 --- a/cmake/GtsamTesting.cmake +++ b/cmake/GtsamTesting.cmake @@ -42,7 +42,7 @@ endmacro() # GTSAM_BUILD_EXAMPLES_ALWAYS is enabled. They may also be built with 'make examples'. # # Usage example: -# gtsamAddExamplesGlob("*.cpp" "BrokenExample.cpp" "gtsam;GeographicLib") +# gtsamAddExamplesGlob("*.cpp" "BrokenExample.cpp" "gtsam;GeographicLib" ON) # # Arguments: # globPatterns: The list of files or glob patterns from which to create examples, with @@ -51,12 +51,9 @@ endmacro() # excludedFiles: A list of files or globs to exclude, e.g. "C*.cpp;BrokenExample.cpp". Pass # an empty string "" if nothing needs to be excluded. # linkLibraries: The list of libraries to link to. -macro(gtsamAddExamplesGlob globPatterns excludedFiles linkLibraries) - if(NOT DEFINED GTSAM_BUILD_EXAMPLES_ALWAYS) - set(GTSAM_BUILD_EXAMPLES_ALWAYS OFF) - endif() - - gtsamAddExesGlob_impl("${globPatterns}" "${excludedFiles}" "${linkLibraries}" "examples" ${GTSAM_BUILD_EXAMPLES_ALWAYS}) +# buildWithAll: Build examples with `make` and/or `make all` +macro(gtsamAddExamplesGlob globPatterns excludedFiles linkLibraries buildWithAll) + gtsamAddExesGlob_impl("${globPatterns}" "${excludedFiles}" "${linkLibraries}" "examples" ${buildWithAll}) endmacro() @@ -80,8 +77,9 @@ endmacro() # excludedFiles: A list of files or globs to exclude, e.g. "C*.cpp;BrokenExample.cpp". Pass # an empty string "" if nothing needs to be excluded. # linkLibraries: The list of libraries to link to. -macro(gtsamAddTimingGlob globPatterns excludedFiles linkLibraries) - gtsamAddExesGlob_impl("${globPatterns}" "${excludedFiles}" "${linkLibraries}" "timing" ${GTSAM_BUILD_TIMING_ALWAYS}) +# buildWithAll: Build examples with `make` and/or `make all` +macro(gtsamAddTimingGlob globPatterns excludedFiles linkLibraries buildWithAll) + gtsamAddExesGlob_impl("${globPatterns}" "${excludedFiles}" "${linkLibraries}" "timing" ${buildWithAll}) endmacro() diff --git a/cmake/README.html b/cmake/README.html index 8170cd489..9cdb5c758 100644 --- a/cmake/README.html +++ b/cmake/README.html @@ -47,9 +47,9 @@ linkLibraries: The list of libraries to link to in addition to CppUnitLite.
  • -

    gtsamAddExamplesGlob(globPatterns excludedFiles linkLibraries) Add scripts that will serve as examples of how to use the library. A list of files or glob patterns is specified, and one executable will be created for each matching .cpp file. These executables will not be installed. They are build with 'make all' if GTSAM_BUILD_EXAMPLES_ALWAYS is enabled. They may also be built with 'make examples'.

    +

    gtsamAddExamplesGlob(globPatterns excludedFiles linkLibraries buildWithAll) Add scripts that will serve as examples of how to use the library. A list of files or glob patterns is specified, and one executable will be created for each matching .cpp file. These executables will not be installed. They are build with 'make all' if GTSAM_BUILD_EXAMPLES_ALWAYS is enabled. They may also be built with 'make examples'.

    Usage example:

    -
    gtsamAddExamplesGlob("*.cpp" "BrokenExample.cpp" "gtsam;GeographicLib")
    +
    gtsamAddExamplesGlob("*.cpp" "BrokenExample.cpp" "gtsam;GeographicLib" ON)
     

    Arguments:

    globPatterns:  The list of files or glob patterns from which to create unit tests, with
    @@ -58,6 +58,7 @@ linkLibraries: The list of libraries to link to in addition to CppUnitLite.
     excludedFiles: A list of files or globs to exclude, e.g. "C*.cpp;BrokenExample.cpp".  Pass
                    an empty string "" if nothing needs to be excluded.
     linkLibraries: The list of libraries to link to.
    +buildWithAll: Build examples with `make` and/or `make all`
     
  • diff --git a/cmake/README.md b/cmake/README.md index 569a401b1..4203b8d3c 100644 --- a/cmake/README.md +++ b/cmake/README.md @@ -52,11 +52,11 @@ Defines two useful functions for creating CTest unit tests. Also immediately cr Pass an empty string "" if nothing needs to be excluded. linkLibraries: The list of libraries to link to in addition to CppUnitLite. -* `gtsamAddExamplesGlob(globPatterns excludedFiles linkLibraries)` Add scripts that will serve as examples of how to use the library. A list of files or glob patterns is specified, and one executable will be created for each matching .cpp file. These executables will not be installed. They are build with 'make all' if GTSAM_BUILD_EXAMPLES_ALWAYS is enabled. They may also be built with 'make examples'. +* `gtsamAddExamplesGlob(globPatterns excludedFiles linkLibraries buildWithAll)` Add scripts that will serve as examples of how to use the library. A list of files or glob patterns is specified, and one executable will be created for each matching .cpp file. These executables will not be installed. They are build with 'make all' if GTSAM_BUILD_EXAMPLES_ALWAYS is enabled. They may also be built with 'make examples'. Usage example: - gtsamAddExamplesGlob("*.cpp" "BrokenExample.cpp" "gtsam;GeographicLib") + gtsamAddExamplesGlob("*.cpp" "BrokenExample.cpp" "gtsam;GeographicLib" ON) Arguments: @@ -66,6 +66,7 @@ Defines two useful functions for creating CTest unit tests. Also immediately cr excludedFiles: A list of files or globs to exclude, e.g. "C*.cpp;BrokenExample.cpp". Pass an empty string "" if nothing needs to be excluded. linkLibraries: The list of libraries to link to. + buildWithAll: Build examples with `make` and/or `make all` ## GtsamMakeConfigFile diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 52d90deb9..280b82265 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -20,4 +20,4 @@ if (NOT GTSAM_USE_BOOST_FEATURES) ) endif() -gtsamAddExamplesGlob("*.cpp" "${excluded_examples}" "gtsam;${Boost_PROGRAM_OPTIONS_LIBRARY}") +gtsamAddExamplesGlob("*.cpp" "${excluded_examples}" "gtsam;${Boost_PROGRAM_OPTIONS_LIBRARY}" ${GTSAM_BUILD_EXAMPLES_ALWAYS}) diff --git a/gtsam_unstable/discrete/examples/CMakeLists.txt b/gtsam_unstable/discrete/examples/CMakeLists.txt index ba4e278c9..16e057cfe 100644 --- a/gtsam_unstable/discrete/examples/CMakeLists.txt +++ b/gtsam_unstable/discrete/examples/CMakeLists.txt @@ -12,4 +12,4 @@ endif() -gtsamAddExamplesGlob("*.cpp" "${excluded_examples}" "gtsam_unstable") +gtsamAddExamplesGlob("*.cpp" "${excluded_examples}" "gtsam_unstable" ${GTSAM_BUILD_EXAMPLES_ALWAYS}) diff --git a/gtsam_unstable/examples/CMakeLists.txt b/gtsam_unstable/examples/CMakeLists.txt index 967937b22..120f293e7 100644 --- a/gtsam_unstable/examples/CMakeLists.txt +++ b/gtsam_unstable/examples/CMakeLists.txt @@ -9,4 +9,4 @@ if (NOT GTSAM_USE_BOOST_FEATURES) endif() -gtsamAddExamplesGlob("*.cpp" "${excluded_examples}" "gtsam_unstable") +gtsamAddExamplesGlob("*.cpp" "${excluded_examples}" "gtsam_unstable" ${GTSAM_BUILD_EXAMPLES_ALWAYS}) diff --git a/gtsam_unstable/timing/CMakeLists.txt b/gtsam_unstable/timing/CMakeLists.txt index b5130ae92..44ea2f5af 100644 --- a/gtsam_unstable/timing/CMakeLists.txt +++ b/gtsam_unstable/timing/CMakeLists.txt @@ -1 +1 @@ -gtsamAddTimingGlob("*.cpp" "" "gtsam_unstable") +gtsamAddTimingGlob("*.cpp" "" "gtsam_unstable" ${GTSAM_BUILD_TIMING_ALWAYS}) diff --git a/timing/CMakeLists.txt b/timing/CMakeLists.txt index e3f97ee0c..f4fbae940 100644 --- a/timing/CMakeLists.txt +++ b/timing/CMakeLists.txt @@ -14,6 +14,6 @@ if (NOT GTSAM_USE_BOOST_FEATURES) ) endif() -gtsamAddTimingGlob("*.cpp" "${excluded_scripts}" "gtsam") +gtsamAddTimingGlob("*.cpp" "${excluded_scripts}" "gtsam" ${GTSAM_BUILD_TIMING_ALWAYS}) target_link_libraries(timeGaussianFactorGraph CppUnitLite) From a30999f1de0a0cb52d1d314990b678365f8e1d33 Mon Sep 17 00:00:00 2001 From: Varun Agrawal Date: Sun, 8 Oct 2023 14:44:38 -0400 Subject: [PATCH 100/113] Move testing cmake flags back to GtsamTesting --- cmake/GtsamTesting.cmake | 12 ++++++++++++ cmake/HandleGeneralOptions.cmake | 9 --------- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/cmake/GtsamTesting.cmake b/cmake/GtsamTesting.cmake index c604b18ab..6fbe14ff3 100644 --- a/cmake/GtsamTesting.cmake +++ b/cmake/GtsamTesting.cmake @@ -88,6 +88,17 @@ endmacro() # Build macros for using tests enable_testing() +#TODO(Varun) Move to HandlePrintConfiguration.cmake. This will require additional changes. +option(GTSAM_BUILD_TESTS "Enable/Disable building of tests" ON) + +# Add option for combining unit tests +if(MSVC OR XCODE_VERSION) + option(GTSAM_SINGLE_TEST_EXE "Combine unit tests into single executable (faster compile)" ON) +else() + option(GTSAM_SINGLE_TEST_EXE "Combine unit tests into single executable (faster compile)" OFF) +endif() +mark_as_advanced(GTSAM_SINGLE_TEST_EXE) + # Enable make check (http://www.cmake.org/Wiki/CMakeEmulateMakeCheck) if(GTSAM_BUILD_TESTS) add_custom_target(check COMMAND ${CMAKE_CTEST_COMMAND} -C $ --output-on-failure) @@ -113,6 +124,7 @@ add_custom_target(timing) # Implementations of this file's macros: macro(gtsamAddTestsGlob_impl groupName globPatterns excludedFiles linkLibraries) + #TODO(Varun) Building of tests should not depend on global gtsam flag if(GTSAM_BUILD_TESTS) # Add group target if it doesn't already exist if(NOT TARGET check.${groupName}) diff --git a/cmake/HandleGeneralOptions.cmake b/cmake/HandleGeneralOptions.cmake index 47a3a597d..302bdf860 100644 --- a/cmake/HandleGeneralOptions.cmake +++ b/cmake/HandleGeneralOptions.cmake @@ -9,17 +9,8 @@ else() endif() ### GtsamTesting related options -option(GTSAM_BUILD_TESTS "Enable/Disable building of tests" ON) option(GTSAM_BUILD_EXAMPLES_ALWAYS "Build examples with 'make all' (build with 'make examples' if not)" ON) option(GTSAM_BUILD_TIMING_ALWAYS "Build timing scripts with 'make all' (build with 'make timing' if not" OFF) - -# Add option for combining unit tests -if(MSVC OR XCODE_VERSION) - option(GTSAM_SINGLE_TEST_EXE "Combine unit tests into single executable (faster compile)" ON) -else() - option(GTSAM_SINGLE_TEST_EXE "Combine unit tests into single executable (faster compile)" OFF) -endif() -mark_as_advanced(GTSAM_SINGLE_TEST_EXE) ### # Add option for using build type postfixes to allow installing multiple build modes From b6dbb0fe92dc4977d2fc135fb8bdffe83993fd24 Mon Sep 17 00:00:00 2001 From: Varun Agrawal Date: Sun, 8 Oct 2023 15:02:01 -0400 Subject: [PATCH 101/113] remove extra spaces --- cmake/GtsamTesting.cmake | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/cmake/GtsamTesting.cmake b/cmake/GtsamTesting.cmake index 6fbe14ff3..47b059213 100644 --- a/cmake/GtsamTesting.cmake +++ b/cmake/GtsamTesting.cmake @@ -91,12 +91,12 @@ enable_testing() #TODO(Varun) Move to HandlePrintConfiguration.cmake. This will require additional changes. option(GTSAM_BUILD_TESTS "Enable/Disable building of tests" ON) -# Add option for combining unit tests -if(MSVC OR XCODE_VERSION) - option(GTSAM_SINGLE_TEST_EXE "Combine unit tests into single executable (faster compile)" ON) -else() - option(GTSAM_SINGLE_TEST_EXE "Combine unit tests into single executable (faster compile)" OFF) -endif() +# Add option for combining unit tests +if(MSVC OR XCODE_VERSION) + option(GTSAM_SINGLE_TEST_EXE "Combine unit tests into single executable (faster compile)" ON) +else() + option(GTSAM_SINGLE_TEST_EXE "Combine unit tests into single executable (faster compile)" OFF) +endif() mark_as_advanced(GTSAM_SINGLE_TEST_EXE) # Enable make check (http://www.cmake.org/Wiki/CMakeEmulateMakeCheck) From d5d75274ecca38266cdd4c6cc2d92d3c1a37a310 Mon Sep 17 00:00:00 2001 From: Varun Agrawal Date: Tue, 10 Oct 2023 06:55:54 -0400 Subject: [PATCH 102/113] rearrange Values::insert to fix 1477 --- gtsam/nonlinear/values.i | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/gtsam/nonlinear/values.i b/gtsam/nonlinear/values.i index e36b1cf1c..64c0f447d 100644 --- a/gtsam/nonlinear/values.i +++ b/gtsam/nonlinear/values.i @@ -66,8 +66,6 @@ class Values { // The order is important: Vector has to precede Point2/Point3 so `atVector` // can work for those fixed-size vectors. - void insert(size_t j, Vector vector); - void insert(size_t j, Matrix matrix); void insert(size_t j, const gtsam::Point2& point2); void insert(size_t j, const gtsam::Point3& point3); void insert(size_t j, const gtsam::Rot2& rot2); @@ -94,6 +92,10 @@ class Values { void insert(size_t j, const gtsam::PinholePose& camera); void insert(size_t j, const gtsam::imuBias::ConstantBias& constant_bias); void insert(size_t j, const gtsam::NavState& nav_state); + // Vector and Matrix versions should go at the end + // to avoid collisions with Point2 and Point3 + void insert(size_t j, Vector vector); + void insert(size_t j, Matrix matrix); void insert(size_t j, double c); template @@ -125,6 +127,8 @@ class Values { void update(size_t j, const gtsam::PinholePose& camera); void update(size_t j, const gtsam::imuBias::ConstantBias& constant_bias); void update(size_t j, const gtsam::NavState& nav_state); + // Vector and Matrix versions should go at the end + // to avoid collisions with Point2 and Point3 void update(size_t j, Vector vector); void update(size_t j, Matrix matrix); void update(size_t j, double c); @@ -165,6 +169,8 @@ class Values { void insert_or_assign(size_t j, const gtsam::imuBias::ConstantBias& constant_bias); void insert_or_assign(size_t j, const gtsam::NavState& nav_state); + // Vector and Matrix versions should go at the end + // to avoid collisions with Point2 and Point3 void insert_or_assign(size_t j, Vector vector); void insert_or_assign(size_t j, Matrix matrix); void insert_or_assign(size_t j, double c); From 87e28ea698b8a20675acc993fc33703e7a2df5ad Mon Sep 17 00:00:00 2001 From: Varun Agrawal Date: Tue, 10 Oct 2023 06:56:17 -0400 Subject: [PATCH 103/113] wrap SmartProjectionPoseFactor::point() --- gtsam/slam/slam.i | 3 +++ 1 file changed, 3 insertions(+) diff --git a/gtsam/slam/slam.i b/gtsam/slam/slam.i index d35a87eee..7135137bb 100644 --- a/gtsam/slam/slam.i +++ b/gtsam/slam/slam.i @@ -156,6 +156,9 @@ virtual class SmartProjectionPoseFactor : gtsam::NonlinearFactor { // enabling serialization functionality void serialize() const; + + gtsam::TriangulationResult point() const; + gtsam::TriangulationResult point(const gtsam::Values& values) const; }; typedef gtsam::SmartProjectionPoseFactor From d16bba321f6bbb80204e27af03521b2de01c84a7 Mon Sep 17 00:00:00 2001 From: Varun Agrawal Date: Tue, 10 Oct 2023 07:42:25 -0400 Subject: [PATCH 104/113] correct method ordering as per documentation --- gtsam/nonlinear/values.i | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/gtsam/nonlinear/values.i b/gtsam/nonlinear/values.i index 64c0f447d..1b0322699 100644 --- a/gtsam/nonlinear/values.i +++ b/gtsam/nonlinear/values.i @@ -59,13 +59,17 @@ class Values { // enabling serialization functionality void serialize() const; - // New in 4.0, we have to specialize every insert/update/at to generate - // wrappers Instead of the old: void insert(size_t j, const gtsam::Value& - // value); void update(size_t j, const gtsam::Value& val); gtsam::Value - // at(size_t j) const; + // New in 4.0, we have to specialize every insert/update/at + // to generate wrappers. + // Instead of the old: + // void insert(size_t j, const gtsam::Value& value); + // void update(size_t j, const gtsam::Value& val); + // gtsam::Value at(size_t j) const; // The order is important: Vector has to precede Point2/Point3 so `atVector` // can work for those fixed-size vectors. + void insert(size_t j, Vector vector); + void insert(size_t j, Matrix matrix); void insert(size_t j, const gtsam::Point2& point2); void insert(size_t j, const gtsam::Point3& point3); void insert(size_t j, const gtsam::Rot2& rot2); @@ -92,15 +96,15 @@ class Values { void insert(size_t j, const gtsam::PinholePose& camera); void insert(size_t j, const gtsam::imuBias::ConstantBias& constant_bias); void insert(size_t j, const gtsam::NavState& nav_state); - // Vector and Matrix versions should go at the end - // to avoid collisions with Point2 and Point3 - void insert(size_t j, Vector vector); - void insert(size_t j, Matrix matrix); void insert(size_t j, double c); template void insert(size_t j, const T& val); + // The order is important: Vector has to precede Point2/Point3 so `atVector` + // can work for those fixed-size vectors. + void update(size_t j, Vector vector); + void update(size_t j, Matrix matrix); void update(size_t j, const gtsam::Point2& point2); void update(size_t j, const gtsam::Point3& point3); void update(size_t j, const gtsam::Rot2& rot2); @@ -127,12 +131,12 @@ class Values { void update(size_t j, const gtsam::PinholePose& camera); void update(size_t j, const gtsam::imuBias::ConstantBias& constant_bias); void update(size_t j, const gtsam::NavState& nav_state); - // Vector and Matrix versions should go at the end - // to avoid collisions with Point2 and Point3 - void update(size_t j, Vector vector); - void update(size_t j, Matrix matrix); void update(size_t j, double c); + // The order is important: Vector has to precede Point2/Point3 so `atVector` + // can work for those fixed-size vectors. + void insert_or_assign(size_t j, Vector vector); + void insert_or_assign(size_t j, Matrix matrix); void insert_or_assign(size_t j, const gtsam::Point2& point2); void insert_or_assign(size_t j, const gtsam::Point3& point3); void insert_or_assign(size_t j, const gtsam::Rot2& rot2); @@ -169,10 +173,6 @@ class Values { void insert_or_assign(size_t j, const gtsam::imuBias::ConstantBias& constant_bias); void insert_or_assign(size_t j, const gtsam::NavState& nav_state); - // Vector and Matrix versions should go at the end - // to avoid collisions with Point2 and Point3 - void insert_or_assign(size_t j, Vector vector); - void insert_or_assign(size_t j, Matrix matrix); void insert_or_assign(size_t j, double c); template Date: Wed, 11 Oct 2023 14:21:59 -0400 Subject: [PATCH 105/113] make DefaultKeyFormatter an extern variable so it can be updated in downstream applications --- gtsam/inference/Key.cpp | 5 ++++- gtsam/inference/Key.h | 18 ++++++++++-------- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/gtsam/inference/Key.cpp b/gtsam/inference/Key.cpp index 8fcea0d05..b5196b73b 100644 --- a/gtsam/inference/Key.cpp +++ b/gtsam/inference/Key.cpp @@ -10,7 +10,7 @@ * -------------------------------------------------------------------------- */ /** - * @file Key.h + * @file Key.cpp * @brief * @author Richard Roberts * @author Alex Cunningham @@ -26,6 +26,9 @@ using namespace std; namespace gtsam { +// KeyFormatter DefaultKeyFormatter = &_dynamicsKeyFormatter; +KeyFormatter DefaultKeyFormatter = &_defaultKeyFormatter; + /* ************************************************************************* */ string _defaultKeyFormatter(Key key) { const Symbol asSymbol(key); diff --git a/gtsam/inference/Key.h b/gtsam/inference/Key.h index 31428a50e..defa47233 100644 --- a/gtsam/inference/Key.h +++ b/gtsam/inference/Key.h @@ -37,10 +37,16 @@ using KeyFormatter = std::function; // Helper function for DefaultKeyFormatter GTSAM_EXPORT std::string _defaultKeyFormatter(Key key); -/// The default KeyFormatter, which is used if no KeyFormatter is passed to -/// a nonlinear 'print' function. Automatically detects plain integer keys -/// and Symbol keys. -static const KeyFormatter DefaultKeyFormatter = &_defaultKeyFormatter; +/** + * The default KeyFormatter, which is used if no KeyFormatter is passed + * to a 'print' function. + * + * Automatically detects plain integer keys and Symbol keys. + * + * Marked as `extern` so that it can be updated by external libraries. + * + */ +extern KeyFormatter DefaultKeyFormatter; // Helper function for Multi-robot Key Formatter GTSAM_EXPORT std::string _multirobotKeyFormatter(gtsam::Key key); @@ -124,7 +130,3 @@ struct traits { }; } // namespace gtsam - - - - From ebf3672c8dc7166dd196e3391f6b43e87c332f60 Mon Sep 17 00:00:00 2001 From: Varun Agrawal Date: Wed, 11 Oct 2023 14:26:39 -0400 Subject: [PATCH 106/113] add comment --- gtsam/inference/Key.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gtsam/inference/Key.cpp b/gtsam/inference/Key.cpp index b5196b73b..15d633eeb 100644 --- a/gtsam/inference/Key.cpp +++ b/gtsam/inference/Key.cpp @@ -26,7 +26,7 @@ using namespace std; namespace gtsam { -// KeyFormatter DefaultKeyFormatter = &_dynamicsKeyFormatter; +/// Assign default key formatter KeyFormatter DefaultKeyFormatter = &_defaultKeyFormatter; /* ************************************************************************* */ From 3c5c500aa5cbf31e5442cc61d32daaedc8440716 Mon Sep 17 00:00:00 2001 From: Varun Agrawal Date: Wed, 11 Oct 2023 18:00:45 -0400 Subject: [PATCH 107/113] mark DefaultKeyFormatter with GTSAM_EXPORT --- gtsam/inference/Key.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gtsam/inference/Key.h b/gtsam/inference/Key.h index defa47233..a02d018f5 100644 --- a/gtsam/inference/Key.h +++ b/gtsam/inference/Key.h @@ -46,7 +46,7 @@ GTSAM_EXPORT std::string _defaultKeyFormatter(Key key); * Marked as `extern` so that it can be updated by external libraries. * */ -extern KeyFormatter DefaultKeyFormatter; +extern GTSAM_EXPORT KeyFormatter DefaultKeyFormatter; // Helper function for Multi-robot Key Formatter GTSAM_EXPORT std::string _multirobotKeyFormatter(gtsam::Key key); From 95add4786a58b7bba9d1d5b4aee44f6d0c9b37cc Mon Sep 17 00:00:00 2001 From: Varun Agrawal Date: Thu, 12 Oct 2023 08:57:38 -0400 Subject: [PATCH 108/113] overload JacobianFactor::getA method with one that takes a key --- gtsam/linear/JacobianFactor.h | 6 ++++++ gtsam/linear/tests/testJacobianFactor.cpp | 9 +++++++++ 2 files changed, 15 insertions(+) diff --git a/gtsam/linear/JacobianFactor.h b/gtsam/linear/JacobianFactor.h index 375e82698..407ed1e27 100644 --- a/gtsam/linear/JacobianFactor.h +++ b/gtsam/linear/JacobianFactor.h @@ -312,6 +312,12 @@ namespace gtsam { /** Get a view of the A matrix */ ABlock getA() { return Ab_.range(0, size()); } + /** + * Get a view of the A matrix for the variable + * pointed to by the given key. + */ + ABlock getA(const Key& key) { return Ab_(find(key) - begin()); } + /** Update an information matrix by adding the information corresponding to this factor * (used internally during elimination). * @param scatter A mapping from variable index to slot index in this HessianFactor diff --git a/gtsam/linear/tests/testJacobianFactor.cpp b/gtsam/linear/tests/testJacobianFactor.cpp index 0ad77b366..234466620 100644 --- a/gtsam/linear/tests/testJacobianFactor.cpp +++ b/gtsam/linear/tests/testJacobianFactor.cpp @@ -65,7 +65,10 @@ TEST(JacobianFactor, constructors_and_accessors) JacobianFactor actual(terms[0].first, terms[0].second, b, noise); EXPECT(assert_equal(expected, actual)); LONGS_EQUAL((long)terms[0].first, (long)actual.keys().back()); + // Key iterator EXPECT(assert_equal(terms[0].second, actual.getA(actual.end() - 1))); + // Key + EXPECT(assert_equal(terms[0].second, actual.getA(terms[0].first))); EXPECT(assert_equal(b, expected.getb())); EXPECT(assert_equal(b, actual.getb())); EXPECT(noise == expected.get_model()); @@ -78,7 +81,10 @@ TEST(JacobianFactor, constructors_and_accessors) terms[1].first, terms[1].second, b, noise); EXPECT(assert_equal(expected, actual)); LONGS_EQUAL((long)terms[1].first, (long)actual.keys().back()); + // Key iterator EXPECT(assert_equal(terms[1].second, actual.getA(actual.end() - 1))); + // Key + EXPECT(assert_equal(terms[1].second, actual.getA(terms[1].first))); EXPECT(assert_equal(b, expected.getb())); EXPECT(assert_equal(b, actual.getb())); EXPECT(noise == expected.get_model()); @@ -91,7 +97,10 @@ TEST(JacobianFactor, constructors_and_accessors) terms[1].first, terms[1].second, terms[2].first, terms[2].second, b, noise); EXPECT(assert_equal(expected, actual)); LONGS_EQUAL((long)terms[2].first, (long)actual.keys().back()); + // Key iterator EXPECT(assert_equal(terms[2].second, actual.getA(actual.end() - 1))); + // Key + EXPECT(assert_equal(terms[2].second, actual.getA(terms[2].first))); EXPECT(assert_equal(b, expected.getb())); EXPECT(assert_equal(b, actual.getb())); EXPECT(noise == expected.get_model()); From 7e1a683e34c853593f9bdacd1f10d799938f458d Mon Sep 17 00:00:00 2001 From: Varun Agrawal Date: Fri, 13 Oct 2023 15:36:45 -0400 Subject: [PATCH 109/113] Revert "imrpove bounds checks in Chebyshev2" This reverts commit 2a386f8631e38de120d67138e6f2bfeb56d0f644. --- gtsam/basis/Chebyshev2.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gtsam/basis/Chebyshev2.cpp b/gtsam/basis/Chebyshev2.cpp index 63fca64cc..44876b6e9 100644 --- a/gtsam/basis/Chebyshev2.cpp +++ b/gtsam/basis/Chebyshev2.cpp @@ -32,7 +32,7 @@ Weights Chebyshev2::CalculateWeights(size_t N, double x, double a, double b) { const double dj = x - Point(N, j, a, b); // only thing that depends on [a,b] - if (std::abs(dj) < 1e-12) { + if (std::abs(dj) < 1e-10) { // exceptional case: x coincides with a Chebyshev point weights.setZero(); weights(j) = 1; @@ -73,7 +73,7 @@ Weights Chebyshev2::DerivativeWeights(size_t N, double x, double a, double b) { for (size_t j = 0; j < N; j++) { const double dj = x - Point(N, j, a, b); // only thing that depends on [a,b] - if (std::abs(dj) < 1e-12) { + if (std::abs(dj) < 1e-10) { // exceptional case: x coincides with a Chebyshev point weightDerivatives.setZero(); // compute the jth row of the differentiation matrix for this point From 1b909d2eea35f985ce3d127a18d2175ff5791b21 Mon Sep 17 00:00:00 2001 From: Varun Agrawal Date: Fri, 13 Oct 2023 15:36:55 -0400 Subject: [PATCH 110/113] Revert "overload the Chebyshev2::Point method to reduce duplication" This reverts commit b2efd64d2bfd8ab378dc8dd16faff971d82391b4. --- gtsam/basis/Chebyshev2.h | 27 ++++++++++++--------------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/gtsam/basis/Chebyshev2.h b/gtsam/basis/Chebyshev2.h index 55bb39488..4c3542d56 100644 --- a/gtsam/basis/Chebyshev2.h +++ b/gtsam/basis/Chebyshev2.h @@ -51,30 +51,27 @@ class GTSAM_EXPORT Chebyshev2 : public Basis { using Parameters = Eigen::Matrix; using DiffMatrix = Eigen::Matrix; - /** - * @brief Specific Chebyshev point, within [a,b] interval. - * Default interval is [-1, 1] - * - * @param N The degree of the polynomial - * @param j The index of the Chebyshev point - * @param a Lower bound of interval (default: -1) - * @param b Upper bound of interval (default: 1) - * @return double - */ - static double Point(size_t N, int j, double a = -1, double b = 1) { + /// Specific Chebyshev point + static double Point(size_t N, int j) { assert(j >= 0 && size_t(j) < N); const double dtheta = M_PI / (N > 1 ? (N - 1) : 1); // We add -PI so that we get values ordered from -1 to +1 - // sin(-M_PI_2 + dtheta*j); also works + // sin(- M_PI_2 + dtheta*j); also works + return cos(-M_PI + dtheta * j); + } + + /// Specific Chebyshev point, within [a,b] interval + static double Point(size_t N, int j, double a, double b) { + assert(j >= 0 && size_t(j) < N); + const double dtheta = M_PI / (N - 1); + // We add -PI so that we get values ordered from -1 to +1 return a + (b - a) * (1. + cos(-M_PI + dtheta * j)) / 2; } /// All Chebyshev points static Vector Points(size_t N) { Vector points(N); - for (size_t j = 0; j < N; j++) { - points(j) = Point(N, j); - } + for (size_t j = 0; j < N; j++) points(j) = Point(N, j); return points; } From 320ac1bc6afb2006e7ca4b911ad8f13586e165bd Mon Sep 17 00:00:00 2001 From: Varun Agrawal Date: Tue, 17 Oct 2023 15:32:56 -0400 Subject: [PATCH 111/113] Revert "re-enable debug CI for linux" This reverts commit 331ae137af844fee6b7604dbddc0a71764fb2880. --- .github/workflows/build-linux.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-linux.yml b/.github/workflows/build-linux.yml index 4502dbe0e..5de09c63f 100644 --- a/.github/workflows/build-linux.yml +++ b/.github/workflows/build-linux.yml @@ -31,7 +31,7 @@ jobs: ubuntu-22.04-clang-14, ] - build_type: [Debug, Release] + build_type: [Release] build_unstable: [ON] include: - name: ubuntu-20.04-gcc-9 From fd02accccb92417599aca82ae83d2ca718ae984f Mon Sep 17 00:00:00 2001 From: Jose Luis Blanco-Claraco Date: Mon, 30 Oct 2023 00:18:45 +0100 Subject: [PATCH 112/113] Fix apparent non-templated type in expressions --- gtsam/slam/expressions.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gtsam/slam/expressions.h b/gtsam/slam/expressions.h index d1bfab7f2..1bed487ee 100644 --- a/gtsam/slam/expressions.h +++ b/gtsam/slam/expressions.h @@ -155,10 +155,10 @@ Point2_ project2(const Expression& camera_, const Expression& p_) namespace internal { // Helper template for project3 expression below template -inline Point2 project6(const Pose3& x, const Point3& p, const Cal3_S2& K, +inline Point2 project6(const Pose3& x, const POINT& p, const CALIBRATION& K, OptionalJacobian<2, 6> Dpose, OptionalJacobian<2, 3> Dpoint, OptionalJacobian<2, 5> Dcal) { - return PinholeCamera(x, K).project(p, Dpose, Dpoint, Dcal); + return PinholeCamera(x, K).project(p, Dpose, Dpoint, Dcal); } } From a06303e55e238bfcb63d3283ea42d94581da7fe7 Mon Sep 17 00:00:00 2001 From: Jose Luis Blanco-Claraco Date: Mon, 30 Oct 2023 01:09:11 +0100 Subject: [PATCH 113/113] Update expressions.h Template dimension too --- gtsam/slam/expressions.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gtsam/slam/expressions.h b/gtsam/slam/expressions.h index 1bed487ee..8d34ce681 100644 --- a/gtsam/slam/expressions.h +++ b/gtsam/slam/expressions.h @@ -157,7 +157,7 @@ namespace internal { template inline Point2 project6(const Pose3& x, const POINT& p, const CALIBRATION& K, OptionalJacobian<2, 6> Dpose, OptionalJacobian<2, 3> Dpoint, - OptionalJacobian<2, 5> Dcal) { + OptionalJacobian<2, CALIBRATION::dimension> Dcal) { return PinholeCamera(x, K).project(p, Dpose, Dpoint, Dcal); } }