From 7c29cd8b20d3ef1f60f56ed8ceaac9c22c647247 Mon Sep 17 00:00:00 2001 From: Logan Drescher Date: Wed, 25 Jun 2025 14:43:51 -0400 Subject: [PATCH 01/34] Updates to build: allow argparse (and integration with CLion) --- .gitignore | 1 + CMakeLists.txt | 15 +++++++++++++-- IDAWin/CMakeLists.txt | 31 +++++++------------------------ conanfile.py | 1 + 4 files changed, 22 insertions(+), 26 deletions(-) diff --git a/.gitignore b/.gitignore index 0f8a2f4ad..f57332c81 100644 --- a/.gitignore +++ b/.gitignore @@ -28,3 +28,4 @@ IDAWin/tests/smoke/SimID_1489333437_0_.ida CMakeUserPresets.json conan-build conan-build/* +conan_provider.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index f0400e856..751592c4c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,7 +3,7 @@ # Top Level CMake File # ############################################## -cmake_minimum_required(VERSION 3.13) +cmake_minimum_required(VERSION 3.14) project(vcell-ode-numerics) set(CMAKE_CXX_STANDARD 20) if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang") @@ -195,10 +195,21 @@ set(DOWNLOAD_EXTRACT_TIMESTAMP true) cmake_policy(SET CMP0135 NEW) ############################################# # -# Declare and install dependency: Google-test +# Declare and dynamically net-install dependencies # ############################################## include(FetchContent) +############################################# +# Argparse +############################################## +FetchContent_Declare( + argparse + GIT_REPOSITORY https://github.com/p-ranav/argparse.git +) +FetchContent_MakeAvailable(argparse) +############################################# +# GoogleTest +############################################## FetchContent_Declare( googletest GIT_REPOSITORY https://github.com/google/googletest.git diff --git a/IDAWin/CMakeLists.txt b/IDAWin/CMakeLists.txt index 0dcf9f0a3..76cf35abc 100644 --- a/IDAWin/CMakeLists.txt +++ b/IDAWin/CMakeLists.txt @@ -20,7 +20,7 @@ set (EXE_SRC_FILES ) add_library(IDAWin STATIC ${SRC_FILES} ${HEADER_FILES}) -target_link_libraries(IDAWin sundials ExpressionParser vcellmessaging) +target_link_libraries(IDAWin sundials ExpressionParser vcellmessaging argparse) set(EXE_FILE SundialsSolverStandalone) if (ARCH_64bit) set(EXE_FILE ${EXE_FILE}_x64) @@ -32,30 +32,13 @@ target_link_libraries(${EXE_FILE} IDAWin) install(TARGETS ${EXE_FILE} RUNTIME DESTINATION ${OPTION_EXE_DIRECTORY}) install(TARGETS IDAWin ARCHIVE DESTINATION bin) - +# # # # # # # # # # # # # # # # # +# smoke test as a python script, for bash test example, see NFsim/tests/smoke +# # # # # # # # # # # # # # # # # enable_testing() - -if (MINGW) - set(test_sundials_exe ${CMAKE_BINARY_DIR}/bin/${EXE_FILE}.exe) - set(python_cmd py) -else (MINGW) - set(test_sundials_exe ${CMAKE_BINARY_DIR}/bin/${EXE_FILE}) - set(python_cmd python3) -endif (MINGW) set(test_dir ${CMAKE_CURRENT_SOURCE_DIR}/tests/smoke) - -# smoke test as a python script, for bash test example, see NFsim/tests/smoke -add_test(NAME ${EXE_FILE}_smoke COMMAND ${python_cmd} ${test_dir}/smoke.py ${test_sundials_exe} WORKING_DIRECTORY ${test_dir}) - - -add_executable( - hello_test - hello_test.cpp -) -target_link_libraries( - hello_test - GTest::gtest_main -) - include(GoogleTest) +add_test(NAME ${EXE_FILE}_smoke COMMAND ${python_cmd} ${test_dir}/smoke.py ${test_sundials_exe} WORKING_DIRECTORY ${test_dir}) +add_executable(hello_test hello_test.cpp) +target_link_libraries(hello_test GTest::gtest_main) gtest_discover_tests(hello_test) \ No newline at end of file diff --git a/conanfile.py b/conanfile.py index 1d76fd804..2e7c89975 100644 --- a/conanfile.py +++ b/conanfile.py @@ -30,6 +30,7 @@ def build(self): cmake.definitions["OPTION_TARGET_DOCS"] = "ON" if self.options.generate_docs else "OFF" def requirements(self): + self.requires("argparse/[>=3.2 <4.0]") if self.options.include_messaging: self.requires("libcurl/[<9.0]") From f3c31071f1acd8787919b9c569ef819a36d3734e Mon Sep 17 00:00:00 2001 From: Logan Drescher Date: Wed, 25 Jun 2025 14:46:43 -0400 Subject: [PATCH 02/34] [WIP] Cleaned up most warnings --- ExpressionParser/ASTAddNode.cpp | 6 +- ExpressionParser/ASTAddNode.h | 4 +- ExpressionParser/ASTAndNode.cpp | 6 +- ExpressionParser/ASTAndNode.h | 4 +- ExpressionParser/ASTExpression.cpp | 6 +- ExpressionParser/ASTExpression.h | 4 +- ExpressionParser/ASTFloatNode.cpp | 16 +- ExpressionParser/ASTFloatNode.h | 4 +- ExpressionParser/ASTFuncNode.cpp | 147 +++-- ExpressionParser/ASTFuncNode.h | 8 +- ExpressionParser/ASTIdNode.cpp | 18 +- ExpressionParser/ASTIdNode.h | 10 +- ExpressionParser/ASTInvertTermNode.cpp | 6 +- ExpressionParser/ASTInvertTermNode.h | 4 +- ExpressionParser/ASTMinusTermNode.cpp | 6 +- ExpressionParser/ASTMinusTermNode.h | 4 +- ExpressionParser/ASTMultNode.cpp | 15 +- ExpressionParser/ASTMultNode.h | 4 +- ExpressionParser/ASTNotNode.cpp | 6 +- ExpressionParser/ASTNotNode.h | 4 +- ExpressionParser/ASTOrNode.cpp | 6 +- ExpressionParser/ASTOrNode.h | 4 +- ExpressionParser/ASTPowerNode.cpp | 16 +- ExpressionParser/ASTPowerNode.h | 4 +- ExpressionParser/ASTRelationalNode.cpp | 8 +- ExpressionParser/ASTRelationalNode.h | 8 +- ExpressionParser/Exception.cpp | 9 +- ExpressionParser/Expression.cpp | 34 +- ExpressionParser/Expression.h | 20 +- ExpressionParser/ExpressionParser.cpp | 16 +- ExpressionParser/ExpressionParser.h | 7 +- ExpressionParser/ExpressionParserConstants.h | 4 +- .../ExpressionParserTokenManager.cpp | 22 +- .../ExpressionParserTokenManager.h | 12 +- .../ExpressionParserTreeConstants.h | 2 +- ExpressionParser/ExpressionTest.cpp | 90 ++- ExpressionParser/JJTExpressionParserState.h | 5 +- ExpressionParser/Node.cpp | 22 +- ExpressionParser/Node.h | 18 +- ExpressionParser/ParseException.cpp | 4 +- ExpressionParser/SimpleCharStream.cpp | 14 +- ExpressionParser/SimpleCharStream.h | 14 +- ExpressionParser/SimpleSymbolTable.cpp | 4 +- ExpressionParser/SimpleSymbolTable.h | 5 +- ExpressionParser/SimpleSymbolTableEntry.cpp | 4 +- ExpressionParser/SimpleSymbolTableEntry.h | 6 +- ExpressionParser/StackMachine.cpp | 54 +- ExpressionParser/SymbolTable.h | 2 +- ExpressionParser/SymbolTableEntry.h | 3 +- ExpressionParser/Token.h | 3 +- ExpressionParserTest/ExpressionParserTest.cpp | 53 +- IDAWin/OdeResultSet.cpp | 212 ++++--- IDAWin/OdeResultSet.h | 113 ++-- IDAWin/StoppedByUserException.cpp | 9 +- IDAWin/StoppedByUserException.h | 7 +- IDAWin/VCellCVodeSolver.cpp | 49 +- IDAWin/VCellCVodeSolver.h | 15 +- IDAWin/VCellIDASolver.cpp | 19 +- IDAWin/VCellIDASolver.h | 3 +- IDAWin/VCellSundialsSolver.cpp | 517 ++++++++---------- IDAWin/VCellSundialsSolver.h | 97 ++-- VCellMessaging/src/SimulationMessaging.cpp | 26 +- 62 files changed, 836 insertions(+), 986 deletions(-) diff --git a/ExpressionParser/ASTAddNode.cpp b/ExpressionParser/ASTAddNode.cpp index da0d3994e..5a3a19a31 100644 --- a/ExpressionParser/ASTAddNode.cpp +++ b/ExpressionParser/ASTAddNode.cpp @@ -13,9 +13,9 @@ ASTAddNode::ASTAddNode(int i) : Node(i) { ASTAddNode::~ASTAddNode() { } -string ASTAddNode::infixString(int lang, NameScope* nameScope) +std::string ASTAddNode::infixString(int lang, NameScope* nameScope) { - string buffer("("); + std::string buffer("("); for (int i = 0;i < jjtGetNumChildren(); i ++){ ASTMinusTermNode* pointer = dynamic_cast(jjtGetChild(i)); if (pointer){ @@ -31,7 +31,7 @@ string ASTAddNode::infixString(int lang, NameScope* nameScope) return buffer; } -void ASTAddNode::getStackElements(vector& elements) { +void ASTAddNode::getStackElements(std::vector& elements) { for (int i = 0;i < jjtGetNumChildren(); i ++){ jjtGetChild(i)->getStackElements(elements); if (i>0) diff --git a/ExpressionParser/ASTAddNode.h b/ExpressionParser/ASTAddNode.h index b6728eaba..85bc7ee7f 100644 --- a/ExpressionParser/ASTAddNode.h +++ b/ExpressionParser/ASTAddNode.h @@ -8,8 +8,8 @@ class ASTAddNode : public Node public: ASTAddNode(int i); ~ASTAddNode(); - string infixString(int lang, NameScope* nameScope); - void getStackElements(vector& elements); + std::string infixString(int lang, NameScope* nameScope); + void getStackElements(std::vector& elements); double evaluate(int evalType, double* values=0); Node* copyTree(); diff --git a/ExpressionParser/ASTAndNode.cpp b/ExpressionParser/ASTAndNode.cpp index f9a9adc84..a4d06ab73 100644 --- a/ExpressionParser/ASTAndNode.cpp +++ b/ExpressionParser/ASTAndNode.cpp @@ -16,10 +16,10 @@ bool ASTAndNode::isBoolean() { return true; } -string ASTAndNode::infixString(int lang, NameScope* nameScope) +std::string ASTAndNode::infixString(int lang, NameScope* nameScope) { - string buffer; + std::string buffer; if(lang == LANGUAGE_VISIT){ for (int i=0;i& elements) { +void ASTAndNode::getStackElements(std::vector& elements) { for (int i=0;igetStackElements(elements);; if (i>0) diff --git a/ExpressionParser/ASTAndNode.h b/ExpressionParser/ASTAndNode.h index 9eb15acbe..5df29e60e 100644 --- a/ExpressionParser/ASTAndNode.h +++ b/ExpressionParser/ASTAndNode.h @@ -8,8 +8,8 @@ class ASTAndNode : public Node public: ASTAndNode(int i); ~ASTAndNode(); - string infixString(int lang, NameScope* nameScope); - void getStackElements(vector& elements); + std::string infixString(int lang, NameScope* nameScope); + void getStackElements(std::vector& elements); double evaluate(int evalType, double* values=0); bool isBoolean(); diff --git a/ExpressionParser/ASTExpression.cpp b/ExpressionParser/ASTExpression.cpp index 87c425dcd..50a276019 100644 --- a/ExpressionParser/ASTExpression.cpp +++ b/ExpressionParser/ASTExpression.cpp @@ -11,9 +11,9 @@ ASTExpression::ASTExpression(int i) : Node(i) { ASTExpression::~ASTExpression() { } -string ASTExpression::infixString(int lang, NameScope* nameScope) +std::string ASTExpression::infixString(int lang, NameScope* nameScope) { - string buffer; + std::string buffer; for (int i = 0; i < jjtGetNumChildren(); i++) { buffer += jjtGetChild(i)->infixString(lang, nameScope); @@ -21,7 +21,7 @@ string ASTExpression::infixString(int lang, NameScope* nameScope) return buffer; } -void ASTExpression::getStackElements(vector& elements) { +void ASTExpression::getStackElements(std::vector& elements) { for (int i = 0; i < jjtGetNumChildren(); i++) { jjtGetChild(i)->getStackElements(elements); } diff --git a/ExpressionParser/ASTExpression.h b/ExpressionParser/ASTExpression.h index 547d6a178..e74aaf2da 100644 --- a/ExpressionParser/ASTExpression.h +++ b/ExpressionParser/ASTExpression.h @@ -8,8 +8,8 @@ class ASTExpression : public Node public: ASTExpression(int i); ~ASTExpression(); - string infixString(int lang, NameScope* nameScope); - void getStackElements(vector& elements); + std::string infixString(int lang, NameScope* nameScope); + void getStackElements(std::vector& elements); double evaluate(int evalType, double* values=0); Node* copyTree(); diff --git a/ExpressionParser/ASTFloatNode.cpp b/ExpressionParser/ASTFloatNode.cpp index b52cbcfa5..df9668fe6 100644 --- a/ExpressionParser/ASTFloatNode.cpp +++ b/ExpressionParser/ASTFloatNode.cpp @@ -1,5 +1,4 @@ -#include - +#include #include "ASTFloatNode.h" #include "RuntimeException.h" #include "ExpressionParserTreeConstants.h" @@ -20,21 +19,16 @@ ASTFloatNode::ASTFloatNode(int i) : Node(i) , value(0) ASTFloatNode::~ASTFloatNode() { } -string ASTFloatNode::infixString(int lang, NameScope* nameScope) +std::string ASTFloatNode::infixString(int lang, NameScope* nameScope) { //if (value == NULL) { // return string("NULL"); //} else - if (value == 0.0) { - return string("0.0"); - } else { - char s[256]; - sprintf(s, "%.20lg\0", value); - return string(s); - } + if (value == 0.0) return std::string{"0.0"}; + return std::format(":.20g", value); } -void ASTFloatNode::getStackElements(vector& elements) { +void ASTFloatNode::getStackElements(std::vector& elements) { elements.push_back(StackElement(value)); } diff --git a/ExpressionParser/ASTFloatNode.h b/ExpressionParser/ASTFloatNode.h index 03082f6eb..e11567a3c 100644 --- a/ExpressionParser/ASTFloatNode.h +++ b/ExpressionParser/ASTFloatNode.h @@ -10,8 +10,8 @@ class ASTFloatNode : public Node ASTFloatNode(int i); ~ASTFloatNode(); double value; - string infixString(int lang, NameScope* nameScope); - void getStackElements(vector& elements); + std::string infixString(int lang, NameScope* nameScope); + void getStackElements(std::vector& elements); double evaluate(int evalType, double* values=0); Node* copyTree(); diff --git a/ExpressionParser/ASTFuncNode.cpp b/ExpressionParser/ASTFuncNode.cpp index d531ba1dd..59bf3a0e6 100644 --- a/ExpressionParser/ASTFuncNode.cpp +++ b/ExpressionParser/ASTFuncNode.cpp @@ -2,10 +2,10 @@ #include #include -using std::min; -using std::max; + #include "ASTFuncNode.h" +#include #include "RuntimeException.h" #include "ExpressionException.h" #include "MathUtil.h" @@ -59,7 +59,7 @@ int StackMachine_LookupTable[] = {TYPE_EXP, TYPE_SQRT, TYPE_ABS, TYPE_POW, TYPE_ACOTH, TYPE_ASECH, TYPE_FACTORIAL, TYPE_J1 }; -const string functionNamesVCML[] = { +const std::string functionNamesVCML[] = { "exp", // 0 "sqrt", // 1 "abs", // 2 @@ -112,10 +112,10 @@ ASTFuncNode::ASTFuncNode(int i) : Node(i) { ASTFuncNode::~ASTFuncNode() { } -void ASTFuncNode::setFunctionFromParserToken(string parserToken) +void ASTFuncNode::setFunctionFromParserToken(std::string parserToken) { for (int i = 0; i < parserNumFunctions; i++){ - string definedToken = functionNamesVCML[i]; + std::string definedToken = functionNamesVCML[i]; if (definedToken == parserToken){ funcType = i; funcName = parserToken; @@ -125,9 +125,8 @@ void ASTFuncNode::setFunctionFromParserToken(string parserToken) throw RuntimeException("unsupported function '" + parserToken + "'"); } -string ASTFuncNode::infixString(int lang, NameScope* nameScope) -{ - string buffer; +std::string ASTFuncNode::infixString(int lang, NameScope* nameScope){ + std::string buffer; switch (funcType) { case POW : @@ -167,7 +166,7 @@ string ASTFuncNode::infixString(int lang, NameScope* nameScope) return buffer; } -void ASTFuncNode::getStackElements(vector& elements) { +void ASTFuncNode::getStackElements(std::vector& elements) { for (int i = 0; i < jjtGetNumChildren(); i++) { jjtGetChild(i)->getStackElements(elements); } @@ -196,53 +195,47 @@ double ASTFuncNode::evaluate(int evalType, double* values) Node* mantissaChild = child0; double exponent = 0.0; double mantissa = 0.0; - ExpressionException* exponentException = 0; - ExpressionException* mantissaException = 0; + ExpressionException* exponentException = nullptr; + ExpressionException* mantissaException = nullptr; try { exponent = exponentChild->evaluate(evalType, values); } catch (ExpressionException& e) { if (evalType == EVALUATE_VECTOR) - throw e; + throw; exponentException = new ExpressionException(e.getMessage()); } try { mantissa = mantissaChild->evaluate(evalType, values); } catch (ExpressionException& e) { if (evalType == EVALUATE_VECTOR) - throw e; + throw; mantissaException = new ExpressionException(e.getMessage()); } - if (exponentException == NULL && mantissaException == NULL) { + if (exponentException == nullptr && mantissaException == nullptr) { if (mantissa < 0.0 && (MathUtil::round(exponent) != exponent)) { - char problem[100]; - sprintf(problem, "pow(u,v) and u=%lf<0 and v=%lf not an integer", mantissa, exponent); - string errorMsg = getFunctionDomainError(problem, values, "u", mantissaChild, "v", exponentChild); + std::string problem{std::format("pow(u,v) and u={}<0 and v={} not an integer", mantissa, exponent)}; + std::string errorMsg = getFunctionDomainError(problem, values, "u", mantissaChild, "v", exponentChild); throw FunctionDomainException(errorMsg); } if (mantissa == 0.0 && exponent < 0) { - char problem[100]; - sprintf(problem, "pow(u,v) and u=0 and v=%lf<0 divide by zero", exponent); - string errorMsg = getFunctionDomainError(problem, values, "u", mantissaChild, "v", exponentChild); + std::string problem{std::format("pow(u,v) and u=0 and v={}<0 divide by zero", mantissa, exponent)}; + std::string errorMsg = getFunctionDomainError(problem, values, "u", mantissaChild, "v", exponentChild); throw FunctionDomainException(errorMsg); } result = pow(mantissa, exponent); if (MathUtil::double_infinity == -result || MathUtil::double_infinity == result || result != result) { - char problem[1000]; - sprintf(problem, "u^v evaluated to %lf, u=%lf, v=%lf", result, mantissa, exponent); - string errorMsg = getFunctionDomainError(problem, values, "u", mantissaChild, "v", exponentChild); + std::string problem = std::format("u^v evaluated to {}, u={}, v={}", result, mantissa, exponent); + std::string errorMsg = getFunctionDomainError(problem, values, "u", mantissaChild, "v", exponentChild); throw FunctionDomainException(errorMsg); } - } else if (exponentException == 0 && exponent == 0.0) { + } else if (exponentException == nullptr && exponent == 0.0) { result = 1.0; - } else if (mantissaException == 0 && mantissa == 1.0) { + } else if (mantissaException == nullptr && mantissa == 1.0) { result = 1.0; } else { - if (mantissaException != NULL) { - throw (*mantissaException); - } else if (exponentException != NULL) { - throw (*exponentException); - } + if (mantissaException != nullptr) throw *mantissaException; + if (exponentException != nullptr) throw *exponentException; } break; } @@ -252,9 +245,8 @@ double ASTFuncNode::evaluate(int evalType, double* values) throw RuntimeException("log() expects 1 argument"); double argument = child0->evaluate(evalType, values); if (argument <= 0.0) { - char problem[1000]; - sprintf(problem, "log(u) and u=%lf <= 0.0 is undefined", argument); - string errorMsg = getFunctionDomainError(problem, values, "u", child0); + std::string problem{std::format("log(u) and u={} <= 0.0 is undefined", argument)}; + std::string errorMsg = getFunctionDomainError(problem, values, "u", child0); throw FunctionDomainException(errorMsg); } result = log(argument); @@ -274,9 +266,8 @@ double ASTFuncNode::evaluate(int evalType, double* values) throw RuntimeException("sqrt() expects 1 argument"); double argument = child0->evaluate(evalType, values); if (argument < 0) { - char problem[1000]; - sprintf(problem, "sqrt(u) where u=%lf<0 is undefined", argument); - string errorMsg = getFunctionDomainError(problem, values, "u", child0); + std::string problem{std::format("sqrt(u) where u={}<0 is undefined", argument)}; + std::string errorMsg = getFunctionDomainError(problem, values, "u", child0); throw FunctionDomainException(errorMsg); } result = sqrt(argument); @@ -312,9 +303,8 @@ double ASTFuncNode::evaluate(int evalType, double* values) throw RuntimeException("asin() expects 1 argument"); double argument = child0->evaluate(evalType, values); if (fabs(argument) > 1.0) { - char problem[1000]; - sprintf(problem, "asin(u) and u=%lf and |u|>1.0 undefined", argument); - string errorMsg = getFunctionDomainError(problem, values, "u", child0); + std::string problem{std::format("asin(u) and u={} and |u|>1.0 undefined", argument)}; + std::string errorMsg = getFunctionDomainError(problem, values, "u", child0); throw FunctionDomainException(errorMsg); } result = asin(argument); @@ -326,9 +316,8 @@ double ASTFuncNode::evaluate(int evalType, double* values) throw RuntimeException("acos() expects 1 argument"); double argument = child0->evaluate(evalType, values); if (fabs(argument) > 1.0) { - char problem[1000]; - sprintf(problem, "acos(u) and u=%lf and |u|>1.0 undefined", argument); - string errorMsg = getFunctionDomainError(problem, values, "u", child0); + std::string problem{std::format("acos(u) and u={} and |u|>1.0 undefined", argument)}; + std::string errorMsg = getFunctionDomainError(problem, values, "u", child0); throw FunctionDomainException(errorMsg); } result = acos(argument); @@ -357,7 +346,7 @@ double ASTFuncNode::evaluate(int evalType, double* values) throw RuntimeException("max() expects 2 arguments"); double argument0 = child0->evaluate(evalType, values); double argument1 = jjtGetChild(1)->evaluate(evalType, values); - result = max(argument0, argument1); + result = std::max(argument0, argument1); break; } case MIN : @@ -366,7 +355,7 @@ double ASTFuncNode::evaluate(int evalType, double* values) throw RuntimeException("min() expects 2 arguments"); double argument0 = child0->evaluate(evalType, values); double argument1 = jjtGetChild(1)->evaluate(evalType, values); - result = min(argument0, argument1); + result = std::min(argument0, argument1); break; } case CEIL : @@ -392,9 +381,8 @@ double ASTFuncNode::evaluate(int evalType, double* values) double argument = child0->evaluate(evalType, values); result = sin(argument); if (result == 0) { - char problem[1000]; - sprintf(problem, "csc(u)=1/sin(u) and sin(u)=0 and u=%lf", argument); - string errorMsg = getFunctionDomainError(problem, values, "u", child0); + std::string problem{std::format("csc(u)=1/sin(u) and sin(u)=0 and u={}", argument)}; + std::string errorMsg = getFunctionDomainError(problem, values, "u", child0); throw FunctionDomainException(errorMsg); } result = 1/result; @@ -407,9 +395,8 @@ double ASTFuncNode::evaluate(int evalType, double* values) double argument = child0->evaluate(evalType, values); result = tan(argument); if (result == 0) { - char problem[1000]; - sprintf(problem, "cot(u)=1/tan(u) and tan(u)=0 and u=%lf", argument); - string errorMsg = getFunctionDomainError(problem, values, "u", child0); + std::string problem{std::format("cot(u)=1/tan(u) and tan(u)=0 and u={}", argument)}; + std::string errorMsg = getFunctionDomainError(problem, values, "u", child0); throw FunctionDomainException(errorMsg); } result = 1/result; @@ -422,9 +409,8 @@ double ASTFuncNode::evaluate(int evalType, double* values) double argument = child0->evaluate(evalType, values); result = cos(argument); if (result == 0) { - char problem[1000]; - sprintf(problem, "sec(u)=1/cos(u) and cos(u)=0 and u=%lf", argument); - string errorMsg = getFunctionDomainError(problem, values, "u", child0); + std::string problem{std::format("sec(u)=1/cos(u) and cos(u)=0 and u={}", argument)}; + std::string errorMsg = getFunctionDomainError(problem, values, "u", child0); throw FunctionDomainException(errorMsg); } result = 1/result; @@ -436,9 +422,8 @@ double ASTFuncNode::evaluate(int evalType, double* values) throw RuntimeException("acsc() expects 1 argument"); double argument = child0->evaluate(evalType, values); if (fabs(argument) < 1.0){ - char problem[1000]; - sprintf(problem, "acsc(u) and -1evaluate(evalType, values); if (argument == 0) { - string errorMsg = getFunctionDomainError("acot(u)=atan(1/u) and u=0", values, "u", child0); + std::string errorMsg = getFunctionDomainError("acot(u)=atan(1/u) and u=0", values, "u", child0); throw FunctionDomainException(errorMsg); } result = MathUtil::acot(argument); @@ -462,9 +447,8 @@ double ASTFuncNode::evaluate(int evalType, double* values) throw RuntimeException("asec() expects 1 argument"); double argument = child0->evaluate(evalType, values); if (fabs(argument) < 1.0){ - char problem[1000]; - sprintf(problem, "asec(u) and -1evaluate(evalType, values); if (argument == 0.0){ - char problem[1000]; - sprintf(problem, "csch(u) and |u| = 0, u=%lf", argument); - string errorMsg = getFunctionDomainError(problem, values, "u", child0); + std::string problem{std::format("csch(u) and |u| = 0, u={}", argument)}; + std::string errorMsg = getFunctionDomainError(problem, values, "u", child0); throw FunctionDomainException(errorMsg); } result = MathUtil::csch(argument); @@ -514,9 +497,8 @@ double ASTFuncNode::evaluate(int evalType, double* values) throw RuntimeException("coth() expects 1 argument"); double argument = child0->evaluate(evalType, values); if (argument == 0.0){ - char problem[1000]; - sprintf(problem, "coth(u) and |u| = 0, u=%lf", argument); - string errorMsg = getFunctionDomainError(problem, values, "u", child0); + std::string problem{std::format("coth(u) and |u| = 0, u={}", argument)}; + std::string errorMsg = getFunctionDomainError(problem, values, "u", child0); throw FunctionDomainException(errorMsg); } result = MathUtil::coth(argument); @@ -545,9 +527,8 @@ double ASTFuncNode::evaluate(int evalType, double* values) throw RuntimeException("acosh() expects 1 argument"); double argument = child0->evaluate(evalType, values); if (argument < 1.0){ - char problem[1000]; - sprintf(problem, "acosh(u) and u=%lf<1.0", argument); - string errorMsg = getFunctionDomainError(problem, values, "u", child0); + std::string problem{std::format("acosh(u) and u={}<1.0", argument)}; + std::string errorMsg = getFunctionDomainError(problem, values, "u", child0); throw FunctionDomainException(errorMsg); } result = MathUtil::acosh(argument); @@ -559,9 +540,8 @@ double ASTFuncNode::evaluate(int evalType, double* values) throw RuntimeException("atanh() expects 1 argument"); double argument = child0->evaluate(evalType, values); if (fabs(argument) >= 1.0){ - char problem[1000]; - sprintf(problem, "atanh(u) and |u| >= 1.0, u=%lf", argument); - string errorMsg = getFunctionDomainError(problem, values, "u", child0); + std::string problem{std::format("atanh(u) and |u| >= 1.0, u={}", argument)}; + std::string errorMsg = getFunctionDomainError(problem, values, "u", child0); throw FunctionDomainException(errorMsg); } result = MathUtil::atanh(argument); @@ -573,7 +553,7 @@ double ASTFuncNode::evaluate(int evalType, double* values) throw RuntimeException("acsch() expects 1 argument"); double argument = child0->evaluate(evalType, values); if (argument == 0.0){ - string errorMsg = getFunctionDomainError("acsch(u) and u=0", values, "u", child0); + std::string errorMsg = getFunctionDomainError("acsch(u) and u=0", values, "u", child0); throw FunctionDomainException(errorMsg); } result = MathUtil::acsch(argument); @@ -585,9 +565,8 @@ double ASTFuncNode::evaluate(int evalType, double* values) throw RuntimeException("acoth() expects 1 argument"); double argument = child0->evaluate(evalType, values); if (fabs(argument) <= 1.0){ - char problem[1000]; - sprintf(problem, "acoth(u) and |u| <= 1.0, u=%lf", argument); - string errorMsg = getFunctionDomainError(problem, values, "u", child0); + std::string problem{std::format("acoth(u) and |u| <= 1.0, u={}", argument)}; + std::string errorMsg = getFunctionDomainError(problem, values, "u", child0); throw FunctionDomainException(errorMsg); } result = MathUtil::acoth(argument); @@ -599,9 +578,8 @@ double ASTFuncNode::evaluate(int evalType, double* values) throw RuntimeException("asech() expects 1 argument"); double argument = child0->evaluate(evalType, values); if (argument <= 0.0 || argument > 1.0){ - char problem[1000]; - sprintf(problem, "asech(u) and u <= 0.0 or u > 1.0, u=%lf", argument); - string errorMsg = getFunctionDomainError(problem, values, "u", child0); + std::string problem{std::format("asech(u) and u <= 0.0 or u > 1.0, u={}", argument)}; + std::string errorMsg = getFunctionDomainError(problem, values, "u", child0); throw FunctionDomainException(errorMsg); } result = MathUtil::asech(argument); @@ -612,10 +590,9 @@ double ASTFuncNode::evaluate(int evalType, double* values) if (jjtGetNumChildren()!= 1) throw RuntimeException("factorial() expects 1 argument"); double argument = child0->evaluate(evalType, values); - if (argument < 0.0 || (argument-(int)argument) != 0){ - char problem[1000]; - sprintf(problem, "factorial(u) and u=%lf < 0.0, or u is not an integer", argument); - string errorMsg = getFunctionDomainError(problem, values, "u", child0); + if (argument < 0.0 || (argument-static_cast(argument)) != 0){ + std::string problem{std::format("factorial(u) and u={} < 0.0, or u is not an integer", argument)}; + std::string errorMsg = getFunctionDomainError(problem, values, "u", child0); throw FunctionDomainException(errorMsg); } result = MathUtil::factorial(argument); @@ -637,8 +614,8 @@ double ASTFuncNode::evaluate(int evalType, double* values) } //result is NAN if (MathUtil::double_infinity == -result || MathUtil::double_infinity == result || result != result) { - char problem[1000]; - sprintf(problem, "%s evaluated to infinity or NaN", infixString(LANGUAGE_DEFAULT,0).c_str(), functionNamesVCML[funcType].c_str()); + //std::string problem{std::format("{} evaluated to infinity or NaN", infixString(LANGUAGE_DEFAULT,0).c_str(), functionNamesVCML[funcType].c_str())}; // unmatched specifier? + std::string problem{std::format("{} evaluated to infinity or NaN", infixString(LANGUAGE_DEFAULT,0).c_str())}; throw FunctionRangeException(problem); } return result; diff --git a/ExpressionParser/ASTFuncNode.h b/ExpressionParser/ASTFuncNode.h index 64914c8ed..6db3c653f 100644 --- a/ExpressionParser/ASTFuncNode.h +++ b/ExpressionParser/ASTFuncNode.h @@ -8,9 +8,9 @@ class ASTFuncNode : public Node public: ASTFuncNode(int i); ~ASTFuncNode(); - void setFunctionFromParserToken(string parserToken); - string infixString(int lang, NameScope* nameScope); - void getStackElements(vector& elements); + void setFunctionFromParserToken(std::string parserToken); + std::string infixString(int lang, NameScope* nameScope); + void getStackElements(std::vector& elements); double evaluate(int evalType, double* values=0); Node* copyTree(); @@ -18,7 +18,7 @@ class ASTFuncNode : public Node private: int funcType; - string funcName; + std::string funcName; ASTFuncNode(); }; diff --git a/ExpressionParser/ASTIdNode.cpp b/ExpressionParser/ASTIdNode.cpp index 57d05cf6e..5dd47fd44 100644 --- a/ExpressionParser/ASTIdNode.cpp +++ b/ExpressionParser/ASTIdNode.cpp @@ -1,4 +1,4 @@ -#include +#include #include "ASTIdNode.h" #include "ExpressionException.h" @@ -23,9 +23,9 @@ ASTIdNode::ASTIdNode(int i) : Node(i), symbolTableEntry(NULL) ASTIdNode::~ASTIdNode() { } -string ASTIdNode::infixString(int lang, NameScope* nameScope) +std::string ASTIdNode::infixString(int lang, NameScope* nameScope) { - string idName(name); + std::string idName(name); return idName; if (nameScope == NULL) { @@ -48,12 +48,12 @@ string ASTIdNode::infixString(int lang, NameScope* nameScope) */ char chrs[20]; sprintf(chrs, "%d\0", lang); - throw RuntimeException(string("Lanaguage '") + chrs + " not supported"); + throw RuntimeException(std::string("Lanaguage '") + chrs + " not supported"); } } -void ASTIdNode::getStackElements(vector& elements) { +void ASTIdNode::getStackElements(std::vector& elements) { if (symbolTableEntry == NULL){ throw ExpressionException("tryin to evaluate unbound identifier '" + infixString(LANGUAGE_DEFAULT, 0)+"'"); } @@ -90,7 +90,7 @@ double ASTIdNode::evaluate(int evalType, double* values) { } } -SymbolTableEntry* ASTIdNode::getBinding(string symbol) +SymbolTableEntry* ASTIdNode::getBinding(std::string symbol) { if (name == symbol){ return symbolTableEntry; @@ -109,13 +109,13 @@ void ASTIdNode::bind(SymbolTable* symbolTable) symbolTableEntry = symbolTable->getEntry(name); if (symbolTableEntry == NULL){ - string id = name; + std::string id = name; throw ExpressionBindingException("error binding identifier '" + id + "'"); } } -void ASTIdNode::getSymbols(vector& symbols, int language, NameScope* nameScope) { - string infix = infixString(language, nameScope); +void ASTIdNode::getSymbols(std::vector& symbols, int language, NameScope* nameScope) { + std::string infix = infixString(language, nameScope); for (int i = 0; i < (int)symbols.size(); i ++) { if (symbols[i] == infix) { return; diff --git a/ExpressionParser/ASTIdNode.h b/ExpressionParser/ASTIdNode.h index 549fbc75f..f6976f7b9 100644 --- a/ExpressionParser/ASTIdNode.h +++ b/ExpressionParser/ASTIdNode.h @@ -10,14 +10,14 @@ class ASTIdNode : public Node public: ASTIdNode(int i); ~ASTIdNode(); - string name; - string infixString(int lang, NameScope* nameScope); + std::string name; + std::string infixString(int lang, NameScope* nameScope); SymbolTableEntry* symbolTableEntry; - SymbolTableEntry* getBinding(string symbol); + SymbolTableEntry* getBinding(std::string symbol); void bind(SymbolTable* symbolTable); - void getStackElements(vector& elements); + void getStackElements(std::vector& elements); double evaluate(int evalType, double* values=0); - void getSymbols(vector& symbols, int language, NameScope* nameScope); + void getSymbols(std::vector& symbols, int language, NameScope* nameScope); Node* copyTree(); bool equals(Node* node); diff --git a/ExpressionParser/ASTInvertTermNode.cpp b/ExpressionParser/ASTInvertTermNode.cpp index 278966254..897dbdde3 100644 --- a/ExpressionParser/ASTInvertTermNode.cpp +++ b/ExpressionParser/ASTInvertTermNode.cpp @@ -14,12 +14,12 @@ ASTInvertTermNode::ASTInvertTermNode(int i) : Node(i) { ASTInvertTermNode::~ASTInvertTermNode() { } -string ASTInvertTermNode::infixString(int lang, NameScope* nameScope) +std::string ASTInvertTermNode::infixString(int lang, NameScope* nameScope) { return jjtGetChild(0)->infixString(lang,nameScope); } -void ASTInvertTermNode::getStackElements(vector& elements) { +void ASTInvertTermNode::getStackElements(std::vector& elements) { jjtGetChild(0)->getStackElements(elements); elements.push_back(StackElement(TYPE_DIV)); } @@ -35,7 +35,7 @@ double ASTInvertTermNode::evaluate(int evalType, double* values) { // // form error message for user's consumption. // - string errorMsg = getFunctionDomainError("divide by zero", 0, "divisor", jjtGetChild(0)); + std::string errorMsg = getFunctionDomainError("divide by zero", 0, "divisor", jjtGetChild(0)); throw DivideByZeroException(errorMsg); } else { return (1.0 / childValue); diff --git a/ExpressionParser/ASTInvertTermNode.h b/ExpressionParser/ASTInvertTermNode.h index f94ee718d..87fb9dcd1 100644 --- a/ExpressionParser/ASTInvertTermNode.h +++ b/ExpressionParser/ASTInvertTermNode.h @@ -8,8 +8,8 @@ class ASTInvertTermNode : public Node public: ASTInvertTermNode(int i); ~ASTInvertTermNode(); - string infixString(int lang, NameScope* nameScope); - void getStackElements(vector& elements); + std::string infixString(int lang, NameScope* nameScope); + void getStackElements(std::vector& elements); double evaluate(int evalType, double* values=0); Node* copyTree(); diff --git a/ExpressionParser/ASTMinusTermNode.cpp b/ExpressionParser/ASTMinusTermNode.cpp index 3fa6acce5..448630d13 100644 --- a/ExpressionParser/ASTMinusTermNode.cpp +++ b/ExpressionParser/ASTMinusTermNode.cpp @@ -12,15 +12,15 @@ ASTMinusTermNode::ASTMinusTermNode(int i) : Node(i) { ASTMinusTermNode::~ASTMinusTermNode() { } -string ASTMinusTermNode::infixString(int lang, NameScope* nameScope) +std::string ASTMinusTermNode::infixString(int lang, NameScope* nameScope) { - string buffer(" - "); + std::string buffer(" - "); buffer += jjtGetChild(0)->infixString(lang,nameScope); return buffer; } -void ASTMinusTermNode::getStackElements(vector& elements) { +void ASTMinusTermNode::getStackElements(std::vector& elements) { jjtGetChild(0)->getStackElements(elements); elements.push_back(StackElement(TYPE_SUB)); } diff --git a/ExpressionParser/ASTMinusTermNode.h b/ExpressionParser/ASTMinusTermNode.h index b88593419..649b4c866 100644 --- a/ExpressionParser/ASTMinusTermNode.h +++ b/ExpressionParser/ASTMinusTermNode.h @@ -8,8 +8,8 @@ class ASTMinusTermNode : public Node public: ASTMinusTermNode(int i); ~ASTMinusTermNode(); - string infixString(int lang, NameScope* nameScope); - void getStackElements(vector& elements); + std::string infixString(int lang, NameScope* nameScope); + void getStackElements(std::vector& elements); double evaluate(int evalType, double* values=0); Node* copyTree(); diff --git a/ExpressionParser/ASTMultNode.cpp b/ExpressionParser/ASTMultNode.cpp index daf267bc5..f68e6f762 100644 --- a/ExpressionParser/ASTMultNode.cpp +++ b/ExpressionParser/ASTMultNode.cpp @@ -7,7 +7,6 @@ //#include "ParseException.h" #include -using std::stringstream; ASTMultNode::ASTMultNode() : Node(JJTMULTNODE) { } @@ -27,7 +26,7 @@ bool ASTMultNode::isBoolean() { return true; } -string ASTMultNode::infixString(int lang, NameScope* nameScope) +std::string ASTMultNode::infixString(int lang, NameScope* nameScope) { bool* boolChildFlags = new bool[jjtGetNumChildren()]; bool bAllBoolean = true; @@ -43,7 +42,7 @@ string ASTMultNode::infixString(int lang, NameScope* nameScope) } - stringstream buffer; + std::stringstream buffer; buffer << "("; if (bAllBoolean || bNoBoolean || (lang != LANGUAGE_C && lang != LANGUAGE_VISIT)) { // old way @@ -59,8 +58,8 @@ string ASTMultNode::infixString(int lang, NameScope* nameScope) } } } else { - stringstream conditionBuffer; - stringstream valueBuffer; + std::stringstream conditionBuffer; + std::stringstream valueBuffer; for (int i=0;i 0) { @@ -97,12 +96,12 @@ string ASTMultNode::infixString(int lang, NameScope* nameScope) } } buffer << ")"; - string s = buffer.str(); + std::string s = buffer.str(); delete [] boolChildFlags; return s; } -void ASTMultNode::getStackElements(vector& elements) { +void ASTMultNode::getStackElements(std::vector& elements) { int startSize = (int)elements.size(); @@ -148,7 +147,7 @@ void ASTMultNode::getStackElements(vector& elements) { if (indexBooleanChildren>0){ int finalSize = (int)elements.size(); int size = finalSize-startSize; - vector::reverse_iterator iter = elements.rbegin(); + std::vector::reverse_iterator iter = elements.rbegin(); for (int offset = 0; offset < size; ++offset) { if ((*iter).type==TYPE_BZ && (*iter).branchOffset==0){ (*iter).branchOffset = offset+1; diff --git a/ExpressionParser/ASTMultNode.h b/ExpressionParser/ASTMultNode.h index 02daa2833..3a2d40046 100644 --- a/ExpressionParser/ASTMultNode.h +++ b/ExpressionParser/ASTMultNode.h @@ -8,8 +8,8 @@ class ASTMultNode : public Node public: ASTMultNode(int i); ~ASTMultNode(); - string infixString(int lang, NameScope* nameScope); - void getStackElements(vector& elements); + std::string infixString(int lang, NameScope* nameScope); + void getStackElements(std::vector& elements); double evaluate(int evalType, double* values=0); bool isBoolean(); diff --git a/ExpressionParser/ASTNotNode.cpp b/ExpressionParser/ASTNotNode.cpp index ba0235b01..7c3daf231 100644 --- a/ExpressionParser/ASTNotNode.cpp +++ b/ExpressionParser/ASTNotNode.cpp @@ -17,9 +17,9 @@ bool ASTNotNode::isBoolean() { return true; } -string ASTNotNode::infixString(int lang, NameScope* nameScope) +std::string ASTNotNode::infixString(int lang, NameScope* nameScope) { - string buffer; + std::string buffer; if (lang == LANGUAGE_VISIT){ buffer.append("not("); }else{ @@ -31,7 +31,7 @@ string ASTNotNode::infixString(int lang, NameScope* nameScope) return buffer; } -void ASTNotNode::getStackElements(vector& elements) { +void ASTNotNode::getStackElements(std::vector& elements) { jjtGetChild(0)->getStackElements(elements); elements.push_back(StackElement(TYPE_NOT)); } diff --git a/ExpressionParser/ASTNotNode.h b/ExpressionParser/ASTNotNode.h index f3e7206d6..006eccf96 100644 --- a/ExpressionParser/ASTNotNode.h +++ b/ExpressionParser/ASTNotNode.h @@ -8,8 +8,8 @@ class ASTNotNode : public Node public: ASTNotNode(int i); ~ASTNotNode(); - string infixString(int lang, NameScope* nameScope); - void getStackElements(vector& elements); + std::string infixString(int lang, NameScope* nameScope); + void getStackElements(std::vector& elements); double evaluate(int evalType, double* values=0); bool isBoolean(); diff --git a/ExpressionParser/ASTOrNode.cpp b/ExpressionParser/ASTOrNode.cpp index 3d1b274a5..211ec6db8 100644 --- a/ExpressionParser/ASTOrNode.cpp +++ b/ExpressionParser/ASTOrNode.cpp @@ -16,9 +16,9 @@ bool ASTOrNode::isBoolean() { return true; } -string ASTOrNode::infixString(int lang, NameScope* nameScope) +std::string ASTOrNode::infixString(int lang, NameScope* nameScope) { - string buffer; + std::string buffer; if(lang == LANGUAGE_VISIT){ for (int i=0;i& elements) { +void ASTOrNode::getStackElements(std::vector& elements) { for (int i=0;igetStackElements(elements);; if (i>0) diff --git a/ExpressionParser/ASTOrNode.h b/ExpressionParser/ASTOrNode.h index 8cc4646d0..1105cf17d 100644 --- a/ExpressionParser/ASTOrNode.h +++ b/ExpressionParser/ASTOrNode.h @@ -8,8 +8,8 @@ class ASTOrNode : public Node public: ASTOrNode(int i); ~ASTOrNode(); - string infixString(int lang, NameScope* nameScope); - void getStackElements(vector& elements); + std::string infixString(int lang, NameScope* nameScope); + void getStackElements(std::vector& elements); double evaluate(int evalType, double* values=0); bool isBoolean(); diff --git a/ExpressionParser/ASTPowerNode.cpp b/ExpressionParser/ASTPowerNode.cpp index f65939045..45811536d 100644 --- a/ExpressionParser/ASTPowerNode.cpp +++ b/ExpressionParser/ASTPowerNode.cpp @@ -18,15 +18,15 @@ ASTPowerNode::ASTPowerNode(int i) : Node(i) { ASTPowerNode::~ASTPowerNode() { } -string ASTPowerNode::infixString(int lang, NameScope* nameScope) +std::string ASTPowerNode::infixString(int lang, NameScope* nameScope) { if (jjtGetNumChildren() != 2) { char ch[20]; sprintf(ch, "%d\0", jjtGetNumChildren()); - throw RuntimeException("There are" + string(ch) + " arguments for the power operator, expecting 2"); + throw RuntimeException("There are" + std::string(ch) + " arguments for the power operator, expecting 2"); } - string buffer; + std::string buffer; if (lang == LANGUAGE_DEFAULT || lang == LANGUAGE_MATLAB) { buffer += "("; buffer += jjtGetChild(0)->infixString(lang, nameScope); @@ -44,7 +44,7 @@ string ASTPowerNode::infixString(int lang, NameScope* nameScope) return buffer; } -void ASTPowerNode::getStackElements(vector& elements) { +void ASTPowerNode::getStackElements(std::vector& elements) { jjtGetChild(0)->getStackElements(elements);; jjtGetChild(1)->getStackElements(elements);; elements.push_back(StackElement(TYPE_POW)); @@ -83,22 +83,22 @@ double ASTPowerNode::evaluate(int evalType, double* values) { if (exponentException == NULL && baseException == NULL) { if (baseValue == 0.0 && exponentValue < 0.0) { - string childString = infixString(LANGUAGE_DEFAULT,0); + std::string childString = infixString(LANGUAGE_DEFAULT,0); char problem[1000]; sprintf(problem, "u^v and u=0 and v=%lf<0", exponentValue); - string errorMsg = getFunctionDomainError(problem, values, "u", baseChild, "v", exponentChild); + std::string errorMsg = getFunctionDomainError(problem, values, "u", baseChild, "v", exponentChild); throw DivideByZeroException(errorMsg); } else if (baseValue < 0.0 && exponentValue != MathUtil::round(exponentValue)) { char problem[1000]; sprintf(problem, "u^v and u=%lf<0 and v=%lf not an integer: undefined", baseValue, exponentValue); - string errorMsg = getFunctionDomainError(problem, values, "u", baseChild, "v", exponentChild); + std::string errorMsg = getFunctionDomainError(problem, values, "u", baseChild, "v", exponentChild); throw FunctionDomainException(errorMsg); } else { double result = pow(baseValue, exponentValue); if (MathUtil::double_infinity == -result || MathUtil::double_infinity == result || result != result) { char problem[1000]; sprintf(problem, "u^v evaluated to %lf, u=%lf, v=%lf", result, baseValue); - string errorMsg = getFunctionDomainError(problem, values, "u", baseChild, "v", exponentChild); + std::string errorMsg = getFunctionDomainError(problem, values, "u", baseChild, "v", exponentChild); throw FunctionDomainException(errorMsg); } return result; diff --git a/ExpressionParser/ASTPowerNode.h b/ExpressionParser/ASTPowerNode.h index 3ee096c40..80f1423ec 100644 --- a/ExpressionParser/ASTPowerNode.h +++ b/ExpressionParser/ASTPowerNode.h @@ -8,8 +8,8 @@ class ASTPowerNode : public Node public: ASTPowerNode(int i); ~ASTPowerNode(); - string infixString(int lang, NameScope* nameScope); - void getStackElements(vector& elements); + std::string infixString(int lang, NameScope* nameScope); + void getStackElements(std::vector& elements); double evaluate(int evalType, double* values=0); Node* copyTree(); diff --git a/ExpressionParser/ASTRelationalNode.cpp b/ExpressionParser/ASTRelationalNode.cpp index 28a7af491..c60d71fec 100644 --- a/ExpressionParser/ASTRelationalNode.cpp +++ b/ExpressionParser/ASTRelationalNode.cpp @@ -32,7 +32,7 @@ bool ASTRelationalNode::isBoolean() { return true; } -void ASTRelationalNode::setOperationFromToken(string op) +void ASTRelationalNode::setOperationFromToken(std::string op) { if (op == ">"){ operation = GT; @@ -53,9 +53,9 @@ void ASTRelationalNode::setOperationFromToken(string op) } -string ASTRelationalNode::infixString(int lang, NameScope* nameScope) +std::string ASTRelationalNode::infixString(int lang, NameScope* nameScope) { - string buffer; + std::string buffer; if(lang == LANGUAGE_VISIT){ if(jjtGetNumChildren() != 2){ throw ParseException("ASTRelationalNode for VISIT expecting 2 children"); @@ -103,7 +103,7 @@ string ASTRelationalNode::infixString(int lang, NameScope* nameScope) return buffer; } -void ASTRelationalNode::getStackElements(vector& elements) { +void ASTRelationalNode::getStackElements(std::vector& elements) { for (int i=0;igetStackElements(elements);; if (i>0) diff --git a/ExpressionParser/ASTRelationalNode.h b/ExpressionParser/ASTRelationalNode.h index 06bdfe397..ead56138b 100644 --- a/ExpressionParser/ASTRelationalNode.h +++ b/ExpressionParser/ASTRelationalNode.h @@ -8,9 +8,9 @@ class ASTRelationalNode : public Node public: ASTRelationalNode(int i); ~ASTRelationalNode(); - void setOperationFromToken(string op); - string infixString(int lang, NameScope* nameScope); - void getStackElements(vector& elements); + void setOperationFromToken(std::string op); + std::string infixString(int lang, NameScope* nameScope); + void getStackElements(std::vector& elements); double evaluate(int evalType, double* values=0); bool isBoolean(); @@ -19,7 +19,7 @@ class ASTRelationalNode : public Node private: int operation; - string opString; + std::string opString; ASTRelationalNode(); }; diff --git a/ExpressionParser/Exception.cpp b/ExpressionParser/Exception.cpp index c380ecf56..0deebdd33 100644 --- a/ExpressionParser/Exception.cpp +++ b/ExpressionParser/Exception.cpp @@ -1,6 +1,5 @@ -#include #include -#include +#include #include "Exception.h" #include "ParserException.h" @@ -29,7 +28,7 @@ VCell::Exception::~Exception(void) throw( ) { } -string VCell::Exception::getExactMessage() { +std::string VCell::Exception::getExactMessage() { return message; } @@ -37,7 +36,7 @@ const char * VCell::Exception::what() const throw( ){ return message.c_str( ); } -string VCell::Exception::getMessage(void) +std::string VCell::Exception::getMessage(void) { return title + " : " + getExactMessage(); } @@ -125,7 +124,7 @@ static void itoa1(int n, char* s, int base) reverse(s); } -string VCell::Exception::add_escapes(string str) +std::string VCell::Exception::add_escapes(string str) { string retval; char ch; diff --git a/ExpressionParser/Expression.cpp b/ExpressionParser/Expression.cpp index 13ca1ad03..a8da926f3 100644 --- a/ExpressionParser/Expression.cpp +++ b/ExpressionParser/Expression.cpp @@ -3,9 +3,7 @@ #include #include #include -using std::cout; -using std::endl; -using std::istringstream; + #include "Expression.h" #include "ExpressionParser.h" @@ -38,7 +36,7 @@ Expression::Expression(const Expression &rhs) * create and bind in single ctor * equivalent to default constructor if #expString is empty */ -Expression::Expression(string expString, SymbolTable & symbolTable) +Expression::Expression(std::string expString, SymbolTable & symbolTable) :rootNode(NULL), stackMachine(NULL) { @@ -48,14 +46,14 @@ Expression::Expression(string expString, SymbolTable & symbolTable) } } -Expression::Expression(string expString) +Expression::Expression(std::string expString) :rootNode(NULL), stackMachine(NULL) { init(expString); } -void Expression::init(const string & expString) { +void Expression::init(const std::string & expString) { if (expString.length() == 0) { throw ParserException("Empty expression"); @@ -77,7 +75,7 @@ void Expression::init(const string & expString) { } } - string trimstr = trim(expString); + std::string trimstr = trim(expString); if (trimstr[trimstr.length() - 1] != ';'){ trimstr += ";"; } @@ -100,7 +98,7 @@ Expression & Expression::operator=(const Expression &rhs) { void Expression::showStackInstructions(void) { getStackMachine()->showInstructions(); - cout.flush(); + std::cout.flush(); } double Expression::evaluateConstant(void) @@ -122,7 +120,7 @@ double Expression::evaluateVectorTree(double* values) } } -string Expression::getEvaluationSummary(double* values) +std::string Expression::getEvaluationSummary(double* values) { return rootNode->getNodeSummary(values, rootNode); } @@ -136,11 +134,11 @@ double Expression::evaluateVector(double* values) } } -void Expression::parseExpression(string exp) +void Expression::parseExpression(std::string exp) { //parseCount++; try { - istringstream iss(exp); + std::istringstream iss(exp); ExpressionParser parser(&iss); delete rootNode; @@ -158,12 +156,12 @@ void Expression::parseExpression(string exp) } } -string Expression::infix(void) +std::string Expression::infix(void) { return rootNode->infixString(LANGUAGE_DEFAULT, 0); } -string Expression::infix_Visit(void) +std::string Expression::infix_Visit(void) { return rootNode->infixString(LANGUAGE_VISIT, 0); } @@ -174,7 +172,7 @@ void Expression::bindExpression(SymbolTable* symbolTable) rootNode->bind(symbolTable); } -string Expression::trim(string str) +std::string Expression::trim(std::string str) { int len = (int)str.length(); int st = 0; @@ -191,11 +189,11 @@ string Expression::trim(string str) inline StackMachine* Expression::getStackMachine() { if (stackMachine == NULL) { - vector elements_vector; + std::vector elements_vector; rootNode->getStackElements(elements_vector); StackElement* elements = new StackElement[elements_vector.size()]; int i = 0; - for (vector::iterator iter = elements_vector.begin(); iter != elements_vector.end(); iter ++) { + for (std::vector::iterator iter = elements_vector.begin(); iter != elements_vector.end(); iter ++) { elements[i ++] = *iter; } stackMachine = new StackMachine(elements, (int)elements_vector.size()); @@ -204,11 +202,11 @@ inline StackMachine* Expression::getStackMachine() { return stackMachine; } -void Expression::getSymbols(vector& symbols) { +void Expression::getSymbols(std::vector& symbols) { rootNode->getSymbols(symbols, LANGUAGE_DEFAULT, 0); } -SymbolTableEntry* Expression::getSymbolBinding(string symbol){ +SymbolTableEntry* Expression::getSymbolBinding(std::string symbol){ return rootNode->getBinding(symbol); } diff --git a/ExpressionParser/Expression.h b/ExpressionParser/Expression.h index 383758141..1938d0984 100644 --- a/ExpressionParser/Expression.h +++ b/ExpressionParser/Expression.h @@ -14,11 +14,11 @@ class Expression { public: Expression(void); - Expression(string expString); + Expression(std::string expString); /** * symbolTable must remain valid memory */ - Expression(string expString, SymbolTable & symbolTable); + Expression(std::string expString, SymbolTable & symbolTable); Expression(Expression* expression); Expression(const Expression &); ~Expression(void); @@ -30,22 +30,22 @@ class Expression // exercise the new way of evaluating vector by using stack machine double evaluateVector(double* values); - string infix(void); + std::string infix(void); /** * symbolTable must remain valid memory */ void bindExpression(SymbolTable* symbolTable); - static string trim(string str); - void getSymbols(vector& symbols); + static std::string trim(std::string str); + void getSymbols(std::vector& symbols); - string getEvaluationSummary(double* values); + std::string getEvaluationSummary(double* values); - SymbolTableEntry* getSymbolBinding(string symbol); + SymbolTableEntry* getSymbolBinding(std::string symbol); double evaluateProxy(); void showStackInstructions(); void substituteInPlace(Expression* origExp, Expression* newExp); - string infix_Visit(void); + std::string infix_Visit(void); bool isConstant( ) const; private: @@ -57,13 +57,13 @@ class Expression //static long derivativeCount; //static long substituteCount; //static long bindCount; - void parseExpression(string exp); + void parseExpression(std::string exp); StackMachine* stackMachine; inline StackMachine* getStackMachine(); /** * common ctor code */ - void init(const string & expString); + void init(const std::string & expString); }; } #endif diff --git a/ExpressionParser/ExpressionParser.cpp b/ExpressionParser/ExpressionParser.cpp index 126840811..b82b26d1a 100644 --- a/ExpressionParser/ExpressionParser.cpp +++ b/ExpressionParser/ExpressionParser.cpp @@ -1,5 +1,7 @@ #include -#include +#include +#include +#include #include "ASTOrNode.h" #include "ASTAndNode.h" @@ -24,7 +26,7 @@ #endif int64 jj_la1_0[] = {0x2000,0x1000,0x30000,0x30000,0xc0000,0xc0000,0x10934000,0x10100000,0x10934000,0x40000000,}; -ExpressionParser::ExpressionParser(istream* stream) +ExpressionParser::ExpressionParser(std::istream* stream) { init(); jj_input_stream = new SimpleCharStream(stream, 1, 1); @@ -539,13 +541,13 @@ void ExpressionParser::jj_add_error_token(int kind, int pos) if (pos == jj_endpos + 1) { jj_lasttokens[jj_endpos++] = kind; } else if (jj_endpos != 0) { - jj_expentry = new vector; + jj_expentry = new std::vector; for (int i = 0; i < jj_endpos; i++) { jj_expentry->push_back(jj_lasttokens[i]); } bool exists = false; - for (vector< vector* >::iterator iter = jj_expentries.begin(); iter != jj_expentries.end(); iter ++) { - vector* oldentry = (vector*)(*iter); + for (std::vector< std::vector* >::iterator iter = jj_expentries.begin(); iter != jj_expentries.end(); iter ++) { + std::vector* oldentry = (std::vector*)(*iter); if (oldentry->size() == jj_expentry->size()) { exists = true; for (unsigned int i = 0; i < jj_expentry->size(); i++) { @@ -976,7 +978,7 @@ ParseException& ExpressionParser::generateParseException(void) } for (int i = 0; i < 31; i++) { if (la1tokens[i]) { - jj_expentry = new vector; + jj_expentry = new std::vector; jj_expentry->push_back(i); jj_expentries.push_back(jj_expentry); } @@ -991,7 +993,7 @@ ParseException& ExpressionParser::generateParseException(void) int* etsLengthArray = new int[numETS]; int** exptokseq = new int*[numETS]; for (int i = 0; i < numETS; i++) { - vector* entry = (vector*)(jj_expentries.at(i)); + std::vector* entry = (std::vector*)(jj_expentries.at(i)); etsLengthArray[i] = (int)entry->size(); exptokseq[i] = new int[etsLengthArray[i]]; for (int j = 0; j < etsLengthArray[i]; j ++) { diff --git a/ExpressionParser/ExpressionParser.h b/ExpressionParser/ExpressionParser.h index 41aaffc02..e4dc45f69 100644 --- a/ExpressionParser/ExpressionParser.h +++ b/ExpressionParser/ExpressionParser.h @@ -2,7 +2,6 @@ #define EXPRESSIONPARSER_H #include -using std::vector; #include "Token.h" #include "ASTExpression.h" @@ -31,7 +30,7 @@ class ExpressionParser { public: ExpressionParser(ExpressionParserTokenManager* tm); - ExpressionParser(istream* stream); + ExpressionParser(std::istream* stream); ~ExpressionParser(); ASTExpression* Expression(void); @@ -70,8 +69,8 @@ class ExpressionParser { LookaheadSuccess jj_ls; - vector< vector* > jj_expentries; - vector *jj_expentry; + std::vector*> jj_expentries; + std::vector *jj_expentry; int jj_kind; int* jj_lasttokens; int jj_endpos; diff --git a/ExpressionParser/ExpressionParserConstants.h b/ExpressionParser/ExpressionParserConstants.h index e629a6d55..004b713d1 100644 --- a/ExpressionParser/ExpressionParserConstants.h +++ b/ExpressionParser/ExpressionParserConstants.h @@ -1,6 +1,8 @@ #ifndef EXPRESSIONPARSERCONSTANTS_H #define EXPRESSIONPARSERCONSTANTS_H +#include + const int EEOF = 0; const int RELATIONAL_OPERATOR = 5; const int LT = 6; @@ -27,7 +29,7 @@ const int DIGIT = 26; const int DEFAULT = 0; -const string tokenImage[] = { +const std::string tokenImage[] = { "", "\" \"", "\"\\t\"", diff --git a/ExpressionParser/ExpressionParserTokenManager.cpp b/ExpressionParser/ExpressionParserTokenManager.cpp index 299b2169f..1484ac744 100644 --- a/ExpressionParser/ExpressionParserTokenManager.cpp +++ b/ExpressionParser/ExpressionParserTokenManager.cpp @@ -8,13 +8,13 @@ int ExpressionParserTokenManager::jjnextStates[] = {35, 36, 41, 42, 31, 32, 31, 32, 33, 22, 23, 39, 40, 43, 44, }; -string* ExpressionParserTokenManager::jjstrLiteralImages[] = { - new string(""), NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, new string("\136"), new string("\53"), new string("\55"), new string("\52"), new string("\57"), - NULL, NULL, NULL, NULL, NULL, NULL, NULL, new string("\73"), new string("\50"), new string("\51"), new string("\54"), +std::string* ExpressionParserTokenManager::jjstrLiteralImages[] = { + new std::string(""), NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, new std::string("\136"), new std::string("\53"), new std::string("\55"), new std::string("\52"), new std::string("\57"), + NULL, NULL, NULL, NULL, NULL, NULL, NULL, new std::string("\73"), new std::string("\50"), new std::string("\51"), new std::string("\54"), }; -string ExpressionParserTokenManager::lexStateNames[] = { +std::string ExpressionParserTokenManager::lexStateNames[] = { "DEFAULT", }; @@ -22,7 +22,7 @@ int64 ExpressionParserTokenManager::jjtoToken[] = { 0x789ff021L, }; int64 ExpressionParserTokenManager::jjtoSkip[] = { 0x1eL, }; -void ExpressionParserTokenManager::setDebugStream(ostream* os) +void ExpressionParserTokenManager::setDebugStream(std::ostream* os) { debugStream = os; } @@ -527,7 +527,7 @@ void ExpressionParserTokenManager::SwitchTo(int lexState) if (lexState >= 1 || lexState < 0) { char ex[20]; sprintf(ex, "%d\0", lexState); - throw RuntimeException("Error: Ignoring invalid lexical state: " + string(ex) + ".State unchanged."); + throw RuntimeException("Error: Ignoring invalid lexical state: " + std::string(ex) + ".State unchanged."); } else curLexState = lexState; @@ -537,7 +537,7 @@ Token* ExpressionParserTokenManager::jjFillToken(void) { Token* t = Token::newToken(jjmatchedKind); t->kind = jjmatchedKind; - string* im = jjstrLiteralImages[jjmatchedKind]; + std::string* im = jjstrLiteralImages[jjmatchedKind]; t->image = (im == NULL) ? input_stream->GetImage() : *im; t->beginLine = input_stream->getBeginLine(); t->beginColumn = input_stream->getBeginColumn(); @@ -590,7 +590,7 @@ Token* ExpressionParserTokenManager::getNextToken(void) } int error_line = input_stream->getEndLine(); int error_column = input_stream->getEndColumn(); - string error_after = ""; + std::string error_after = ""; bool EOFSeen = false; try { input_stream->readChar(); @@ -613,8 +613,8 @@ Token* ExpressionParserTokenManager::getNextToken(void) if (EOFSeen) sprintf(chrs, "Lexical error at line %d, column %d. Encountered: \0", error_line, error_column); else { - string a = Exception::add_escapes(string(&curChar, 1)); - string b = Exception::add_escapes(error_after); + std::string a = Exception::add_escapes(std::string(&curChar, 1)); + std::string b = Exception::add_escapes(error_after); sprintf(chrs, "Lexical error at line %d, column %d. Encountered: \"%s\" (%d) after : \"%s\"\0", error_line, error_column, a.c_str(), curChar, b.c_str()); } diff --git a/ExpressionParser/ExpressionParserTokenManager.h b/ExpressionParser/ExpressionParserTokenManager.h index aeb7d4872..1c4bec14d 100644 --- a/ExpressionParser/ExpressionParserTokenManager.h +++ b/ExpressionParser/ExpressionParserTokenManager.h @@ -3,8 +3,8 @@ #include #include -using std::string; -using std::ostream; + + #include "Token.h" #include "SimpleCharStream.h" @@ -33,10 +33,10 @@ class ExpressionParserTokenManager static int64 jjtoSkip[]; char curChar; - static string* jjstrLiteralImages[]; - static string lexStateNames[]; - ostream* debugStream; - void setDebugStream(ostream* os); + static std::string* jjstrLiteralImages[]; + static std::string lexStateNames[]; + std::ostream* debugStream; + void setDebugStream(std::ostream* os); void ReInit(SimpleCharStream* stream); void ReInit(SimpleCharStream* stream, int lexState); void SwitchTo(int lexState); diff --git a/ExpressionParser/ExpressionParserTreeConstants.h b/ExpressionParser/ExpressionParserTreeConstants.h index 591d78555..b9622f8b3 100644 --- a/ExpressionParser/ExpressionParserTreeConstants.h +++ b/ExpressionParser/ExpressionParserTreeConstants.h @@ -16,7 +16,7 @@ const int JJTFUNCNODE = 11; const int JJTFLOATNODE = 12; const int JJTIDNODE = 13; -const string jjtNodeName[] = { +const std::string jjtNodeName[] = { "Expression", "void", "OrNode", diff --git a/ExpressionParser/ExpressionTest.cpp b/ExpressionParser/ExpressionTest.cpp index b7f8905ca..8f4899132 100644 --- a/ExpressionParser/ExpressionTest.cpp +++ b/ExpressionParser/ExpressionTest.cpp @@ -1,6 +1,4 @@ #include -#include -#include #include "MathUtil.h" #include "Exception.h" @@ -11,11 +9,9 @@ #include "IOException.h" #include #include +#include #include -using std::cout; -using std::endl; -using std::max; -using std::ifstream; + ExpressionTest::ExpressionTest(void) { @@ -29,9 +25,9 @@ void ExpressionTest::testEvaluateVector(void) { try { double d = 0.0; - cout << "Parser: evaluating vector" << endl; + std::cout << "Parser: evaluating vector" << std::endl; Expression* exp = new Expression("a+b/c"); - string ss[] = { "a", "b", "c" }; + std::string ss[] = { "a", "b", "c" }; SimpleSymbolTable* simpleSymbolTable = new SimpleSymbolTable(ss, 3); const int n = 3; @@ -41,7 +37,7 @@ void ExpressionTest::testEvaluateVector(void) for (int i = 0; i < n; i ++) { d = exp->evaluateVector(v[i]); - cout << i << "." << exp->infix() << " = " << d << endl; + std::cout << i << "." << exp->infix() << " = " << d << std::endl; } delete exp; delete simpleSymbolTable; @@ -56,10 +52,10 @@ void ExpressionTest::testEvaluateConstant(void) { try { double d = 0.0; - cout << "Parser: evaluating constant" << endl; + std::cout << "Parser: evaluating constant" << std::endl; Expression* exp = new Expression("(3/5"); d = exp->evaluateConstant(); - cout << exp->infix() << " = " << d << endl; + std::cout << exp->infix() << " = " << d << std::endl; delete exp; } catch (Exception& ex) { Exception::rethrowException(ex); @@ -70,7 +66,7 @@ void ExpressionTest::testEvaluateConstant(void) void ExpressionTest::testParser(int count, char* javaresult, double cvalue, char* expStr, SymbolTable* symbolTable, double* values) { Expression* exp = 0; - string badmsg; + std::string badmsg; try { double javavalue = -0.0; double d = 0.0; @@ -85,14 +81,14 @@ void ExpressionTest::testParser(int count, char* javaresult, double cvalue, char } else { n = sscanf(javaresult, "%lg", &javavalue); if (n != 1) { - cout << " Not a Number:: " << javaresult ; + std::cout << " Not a Number:: " << javaresult ; return; } } bool bException = false; - string before = Expression::trim(string(expStr)); + std::string before = Expression::trim(std::string(expStr)); exp = new Expression(expStr); - string exceptionMsg = ""; + std::string exceptionMsg = ""; try { exp->bindExpression(symbolTable); d = exp->evaluateVector(values); @@ -105,7 +101,7 @@ void ExpressionTest::testParser(int count, char* javaresult, double cvalue, char //cout << count << " EVAL_YES :: all NaN" << endl; } else if (javavalue == d && d == cvalue && d == dtree || fabs(javavalue - d) < 1E-14 && fabs(cvalue - d) < 1E-14 - || fabs(javavalue - d)/max(fabs(d),fabs(javavalue)) < 1E-14 && fabs(cvalue - d)/max(fabs(d),fabs(cvalue)) < 1E-14 ) { + || fabs(javavalue - d)/std::max(fabs(d),fabs(javavalue)) < 1E-14 && fabs(cvalue - d)/std::max(fabs(d),fabs(cvalue)) < 1E-14 ) { //cout << count << " EVAL_YES " << endl; } else { if (bException) { @@ -116,16 +112,16 @@ void ExpressionTest::testParser(int count, char* javaresult, double cvalue, char //printf("Java/Infix_C/C++: %.20g/%.20g/Exception\n", count, cvalue, javavalue); //cout << expStr << endl; } else { - cout << endl << "-------------------------------------------" << endl; + std::cout << std::endl << "-------------------------------------------" << std::endl; printf("%d. EVAL_NO Java/Infix_C/C++ Tree/C++: %.20lg/%.20lg/%.20lg/%.20lg\n", count, javavalue, cvalue, dtree, d); printf("Java~Infix_C/Java~C++/Infix_C~C++: %.20lg/%.20lg/%.20lg/%.20lg\n", fabs((javavalue-cvalue)/javavalue), fabs((javavalue-d)/javavalue), fabs((dtree-cvalue)/cvalue), fabs((d-cvalue)/cvalue)); - cout << expStr << endl; + std::cout << expStr << std::endl; } } delete exp; } catch (Exception& ex) { - cout << ex.getMessage() << endl; + std::cout << ex.getMessage() << std::endl; } } @@ -138,7 +134,7 @@ void ExpressionTest::testParser(char* filename) printf("%s=%.20lg\n", exp->infix().c_str(), d); */ - string ids[] = {"id_0", "id_1", "id_2", "id_3", + std::string ids[] = {"id_0", "id_1", "id_2", "id_3", "id_4", "id_5", "id_6", "id_7", "id_8", "id_9"}; SimpleSymbolTable* symbolTable = new SimpleSymbolTable(ids, 10); @@ -146,12 +142,12 @@ void ExpressionTest::testParser(char* filename) const int n = 10; double v[m][n] = {{0,1,2,3,4,5,6,7,8,9 },{ 1,2,3,4,5,6,7,8,9,10}}; - ifstream ifs(filename); + std::ifstream ifs(filename); if (!ifs.is_open()) { - throw IOException(string("") + "Can't open file '" + filename); + throw IOException(std::string("") + "Can't open file '" + filename); } int count = 0; - vector badmsg; + std::vector badmsg; bool bInfinity = false; bool bNAN = false; bool bException = false; @@ -164,7 +160,7 @@ void ExpressionTest::testParser(char* filename) count ++; bInfinity = false; bNAN = false; - cout << count << "...."; + std::cout << count << "...."; memset(line, 0, 1000*sizeof(char)); ifs >> line; int n = -1; @@ -175,19 +171,19 @@ void ExpressionTest::testParser(char* filename) } else { n = sscanf(line, "%lg", &value); if (n != 1) { - cout << " Not a Number:: " << line ; + std::cout << " Not a Number:: " << line ; } } memset(line, 0, 1000*sizeof(char)); ifs.getline(line, 1000); if (n != 1 && !bInfinity && !bNAN) { - cout << line; + std::cout << line; goto label_1; } bException = false; - string before = Expression::trim(string(line)); + std::string before = Expression::trim(std::string(line)); exp = new Expression(line); - string exceptionMsg = ""; + std::string exceptionMsg = ""; try { exp->bindExpression(symbolTable); d = exp->evaluateVector(v[0]); @@ -196,51 +192,51 @@ void ExpressionTest::testParser(char* filename) exceptionMsg = ex.getMessage(); } if ((MathUtil::double_infinity == d || MathUtil::double_infinity == -d) && bInfinity) { - cout << " INFINITY:::::::::::::Before/After: Infinity/" << d; + std::cout << " INFINITY:::::::::::::Before/After: Infinity/" << d; } else if (d != d && bNAN) { - cout << " NaN:::::::::::::Before/After: NaN/" << d; - } else if (value == d || fabs(value - d) < 1E-14 || fabs(value - d)/max(fabs(d),fabs(value)) < 1E-14) { - cout << " EVAL_YES "; + std::cout << " NaN:::::::::::::Before/After: NaN/" << d; + } else if (value == d || fabs(value - d) < 1E-14 || fabs(value - d)/std::max(fabs(d),fabs(value)) < 1E-14) { + std::cout << " EVAL_YES "; } else { - string afterparsing = exp->infix(); + std::string afterparsing = exp->infix(); if (before == afterparsing) { - cout << "INFIX_YES " << endl; + std::cout << "INFIX_YES " << std::endl; } else { - cout << "INFIX_NO " << endl; + std::cout << "INFIX_NO " << std::endl; } - cout << "Before: " << before << endl; - cout << "After : " << exp->infix() << endl; + std::cout << "Before: " << before << std::endl; + std::cout << "After : " << exp->infix() << std::endl; if (bException) { char chrs[2560]; sprintf(chrs, "%d. C++ throws exception: %s", count, exceptionMsg.c_str()); badmsg.push_back(chrs); - cout << chrs << " EVAL_NO "; + std::cout << chrs << " EVAL_NO "; printf("Before/After: %.20g/Exception ", value); } else if (bInfinity) { char chrs[256]; sprintf(chrs, "%d. Java evaluation is Infinity", count); badmsg.push_back(chrs); - cout << chrs << " EVAL_NO "; + std::cout << chrs << " EVAL_NO "; printf("Before/After: Infinity/%.20lg ", d); } else if (bNAN) { char chrs[256]; sprintf(chrs, "%d. Java evaluation is NAN", count); badmsg.push_back(chrs); - cout << chrs << " EVAL_NO "; + std::cout << chrs << " EVAL_NO "; printf("Before/After: NAN/%.20lg ", d); } else { - double m = max(fabs(d),fabs(value)); + double m = std::max(fabs(d),fabs(value)); if (m < 1e-100) { char chrs[256]; sprintf(chrs, "%d. abs(error)=%lg", count, fabs(d-value)); badmsg.push_back(chrs); - cout << chrs << " EVAL_NO "; + std::cout << chrs << " EVAL_NO "; } else { char chrs[256]; sprintf(chrs, "%d. relative(error)=%lg", count, fabs((d-value)/m)); badmsg.push_back(chrs); - cout << chrs << " EVAL_NO "; + std::cout << chrs << " EVAL_NO "; } printf("Before/After: %.20lg/%.20lg ", value, d); } @@ -248,13 +244,13 @@ void ExpressionTest::testParser(char* filename) delete exp; } catch (Exception& ex) { - cout << ex.getMessage() << endl; + std::cout << ex.getMessage() << std::endl; } label_1: - cout << endl << "-------------------------------------------" << endl; + std::cout << std::endl << "-------------------------------------------" << std::endl; } - cout << "BAD " << badmsg.size() << endl; + std::cout << "BAD " << badmsg.size() << std::endl; for (int i = 0; i < badmsg.size(); i ++) { - cout << badmsg[i] << endl; + std::cout << badmsg[i] << std::endl; } } diff --git a/ExpressionParser/JJTExpressionParserState.h b/ExpressionParser/JJTExpressionParserState.h index 4b4b76d86..6ae92e293 100644 --- a/ExpressionParser/JJTExpressionParserState.h +++ b/ExpressionParser/JJTExpressionParserState.h @@ -2,7 +2,6 @@ #define JJEXPRESSIONPARSERSTATE_H #include -using std::vector; #include "Node.h" @@ -25,8 +24,8 @@ class JJTExpressionParserState private: void popMark(void); - vector nodes; - vector marks; + std::vector nodes; + std::vector marks; int sp; int mk; bool node_created; diff --git a/ExpressionParser/Node.cpp b/ExpressionParser/Node.cpp index 2bfc7c730..9589a3922 100644 --- a/ExpressionParser/Node.cpp +++ b/ExpressionParser/Node.cpp @@ -3,8 +3,6 @@ #include #include #include -using std::cout; -using std::endl; #include "Node.h" #include "Expression.h" @@ -77,8 +75,8 @@ int Node::jjtGetNumChildren() { return numChildren; } -void Node::dump(string prefix) { - cout << toString(prefix) << endl; +void Node::dump(std::string prefix) { + std::cout << toString(prefix) << std::endl; if (children != 0) { for (int i = 0; i < numChildren; ++i) { Node* n = children[i]; @@ -89,19 +87,19 @@ void Node::dump(string prefix) { } } -string Node::toString(string prefix) +std::string Node::toString(std::string prefix) { return prefix + infixString(LANGUAGE_DEFAULT, 0); } -void Node::getSymbols(vector& symbols, int language, NameScope* nameScope) +void Node::getSymbols(std::vector& symbols, int language, NameScope* nameScope) { for (int i=0;igetSymbols(symbols, language, nameScope); } } -SymbolTableEntry* Node::getBinding(string symbol) +SymbolTableEntry* Node::getBinding(std::string symbol) { for (int i=0;igetBinding(symbol); @@ -123,8 +121,8 @@ bool Node::isBoolean() { return false; } -string Node::getFunctionDomainError(string problem, double* values, string argumentName1, Node* node1, string argumentName2, Node* node2){ - string errorMsg = problem + ": " + argumentName1 + "=" + getNodeSummary(values, node1); +std::string Node::getFunctionDomainError(std::string problem, double* values, std::string argumentName1, Node* node1, std::string argumentName2, Node* node2){ + std::string errorMsg = problem + ": " + argumentName1 + "=" + getNodeSummary(values, node1); if (node2 == 0) { return errorMsg; } @@ -132,9 +130,9 @@ string Node::getFunctionDomainError(string problem, double* values, string argum return errorMsg; } -string Node::getNodeSummary(double* values, Node* node){ - string errorMsg; - vector symbols; +std::string Node::getNodeSummary(double* values, Node* node){ + std::string errorMsg; + std::vector symbols; node->getSymbols(symbols, LANGUAGE_DEFAULT, 0); if (symbols.size() > 0) { errorMsg += "\"" + node->infixString(LANGUAGE_DEFAULT, 0) + "\"\n where:\n"; diff --git a/ExpressionParser/Node.h b/ExpressionParser/Node.h index 869c8c4bf..5fb203cd8 100644 --- a/ExpressionParser/Node.h +++ b/ExpressionParser/Node.h @@ -2,8 +2,6 @@ #define SIMPLENODE_H #include #include -using std::string; -using std::vector; #define LANGUAGE_DEFAULT 0 #define LANGUAGE_C 1 @@ -30,7 +28,7 @@ class Node Node(int unused); virtual ~Node(void); virtual Node* copyTree()=0; - virtual void getStackElements(vector& elements)=0; + virtual void getStackElements(std::vector& elements)=0; virtual double evaluate(int type, double* values=0)=0; void jjtOpen(); @@ -46,14 +44,14 @@ class Node Node* abandonChild(int i); Node* jjtGetChild(int i); int jjtGetNumChildren(); - void dump(string prefix); - virtual string infixString(int lang, NameScope* nameScope)=0; - string toString(string prefix); - virtual void getSymbols(vector& symbols, int language, NameScope* nameScope); - virtual SymbolTableEntry* getBinding(string symbol); + void dump(std::string prefix); + virtual std::string infixString(int lang, NameScope* nameScope)=0; + std::string toString(std::string prefix); + virtual void getSymbols(std::vector& symbols, int language, NameScope* nameScope); + virtual SymbolTableEntry* getBinding(std::string symbol); virtual void bind(SymbolTable* symbolTable); - static string getFunctionDomainError(string problem, double* values, string argumentName1, Node* node1, string argumentName2="", Node* node2=0); - static string getNodeSummary(double* values, Node* node); + static std::string getFunctionDomainError(std::string problem, double* values, std::string argumentName1, Node* node1, std::string argumentName2="", Node* node2=0); + static std::string getNodeSummary(double* values, Node* node); virtual bool isBoolean(); void jjtAddChild(Node* n); diff --git a/ExpressionParser/ParseException.cpp b/ExpressionParser/ParseException.cpp index 913518e33..1c1a77def 100644 --- a/ExpressionParser/ParseException.cpp +++ b/ExpressionParser/ParseException.cpp @@ -2,7 +2,7 @@ #include "ParseException.h" -string ParseException::eol = string("\n"); +std::string ParseException::eol = string("\n"); ParseException::ParseException() : Exception("ParseException", "") { @@ -38,7 +38,7 @@ ParseException::ParseException(Token* currentTokenVal, int** expectedTokenSequen tokenImage = tokenImageVal; } -string ParseException::getExactMessage(void) +std::string ParseException::getExactMessage(void) { if (!specialConstructor) { return Exception::getExactMessage(); diff --git a/ExpressionParser/SimpleCharStream.cpp b/ExpressionParser/SimpleCharStream.cpp index bc1e03f3c..9d17c551a 100644 --- a/ExpressionParser/SimpleCharStream.cpp +++ b/ExpressionParser/SimpleCharStream.cpp @@ -7,11 +7,11 @@ bool SimpleCharStream::staticFlag = false; -SimpleCharStream::SimpleCharStream(istream* dstream, int startline, int startcolumn, int buffersize) +SimpleCharStream::SimpleCharStream(std::istream* dstream, int startline, int startcolumn, int buffersize) { init(dstream, startline, startcolumn, buffersize); } -void SimpleCharStream::init(istream* dstream, int startline, int startcolumn, int buffersize) { +void SimpleCharStream::init(std::istream* dstream, int startline, int startcolumn, int buffersize) { tokenBegin = 0; bufpos = -1; prevCharIsCR = false; @@ -34,12 +34,12 @@ void SimpleCharStream::init(istream* dstream, int startline, int startcolumn, i memset(bufcolumn, 0, buffersize * sizeof(int)); } -SimpleCharStream::SimpleCharStream(istream* dstream, int startline, int startcolumn) +SimpleCharStream::SimpleCharStream(std::istream* dstream, int startline, int startcolumn) { init(dstream, startline, startcolumn, 4096); } -SimpleCharStream::SimpleCharStream(istream* dstream) +SimpleCharStream::SimpleCharStream(std::istream* dstream) { init(dstream, 1, 1, 4096); } @@ -254,12 +254,12 @@ void SimpleCharStream::backup(int amount) bufpos += bufsize; } -string SimpleCharStream::GetImage(void) +std::string SimpleCharStream::GetImage(void) { if (bufpos >= tokenBegin) - return string(buffer, tokenBegin, bufpos - tokenBegin + 1); + return std::string(buffer, tokenBegin, bufpos - tokenBegin + 1); else - return string(buffer, tokenBegin, bufsize - tokenBegin) + string(buffer, 0, bufpos + 1); + return std::string(buffer, tokenBegin, bufsize - tokenBegin) + std::string(buffer, 0, bufpos + 1); } char* SimpleCharStream::GetSuffix(int len) diff --git a/ExpressionParser/SimpleCharStream.h b/ExpressionParser/SimpleCharStream.h index dc17afc73..1c036a1ae 100644 --- a/ExpressionParser/SimpleCharStream.h +++ b/ExpressionParser/SimpleCharStream.h @@ -3,8 +3,6 @@ #include #include -using std::istream; -using std::string; class SimpleCharStream { @@ -20,7 +18,7 @@ class SimpleCharStream int line; bool prevCharIsCR; bool prevCharIsLF; - istream* inputStream; + std::istream* inputStream; char* buffer; int maxNextCharInd; int inBuf; @@ -28,12 +26,12 @@ class SimpleCharStream void ExpandBuff(bool wrapAround); void FillBuff(void); void UpdateLineColumn(char c); - void init(istream* dstream, int startline, int startcolumn, int buffersize); + void init(std::istream* dstream, int startline, int startcolumn, int buffersize); public: - SimpleCharStream(istream* dstream, int startline, int startcolumn, int buffersize); - SimpleCharStream(istream* dstream, int startline, int startcolumn); - SimpleCharStream(istream* dstream); + SimpleCharStream(std::istream* dstream, int startline, int startcolumn, int buffersize); + SimpleCharStream(std::istream* dstream, int startline, int startcolumn); + SimpleCharStream(std::istream* dstream); ~SimpleCharStream(void); static bool staticFlag; @@ -48,7 +46,7 @@ class SimpleCharStream int getBeginColumn(void); int getBeginLine(void); void backup(int amount); - string GetImage(void); + std::string GetImage(void); char* GetSuffix(int len); void Done(void); void adjustBeginLineColumn(int newLine, int newCol); diff --git a/ExpressionParser/SimpleSymbolTable.cpp b/ExpressionParser/SimpleSymbolTable.cpp index 4b9360aff..f0cce92ff 100644 --- a/ExpressionParser/SimpleSymbolTable.cpp +++ b/ExpressionParser/SimpleSymbolTable.cpp @@ -1,6 +1,6 @@ #include "SimpleSymbolTable.h" -SimpleSymbolTable::SimpleSymbolTable(string* symbols, int symbolCount, ValueProxy** valueProxies) +SimpleSymbolTable::SimpleSymbolTable(std::string* symbols, int symbolCount, ValueProxy** valueProxies) { for (int i = 0; i < symbolCount; i ++){ steArray.push_back(new SimpleSymbolTableEntry(symbols[i],i,0, valueProxies == 0 ? 0 : valueProxies[i])); @@ -16,7 +16,7 @@ SimpleSymbolTable::~SimpleSymbolTable(void) } -SymbolTableEntry* SimpleSymbolTable::getLocalEntry(const string & identifier) const +SymbolTableEntry* SimpleSymbolTable::getLocalEntry(const std::string & identifier) const { for (unsigned int i = 0; i < steArray.size(); i++){ if (steArray[i]->getName() == identifier){ diff --git a/ExpressionParser/SimpleSymbolTable.h b/ExpressionParser/SimpleSymbolTable.h index 61399e895..1d27a22f7 100644 --- a/ExpressionParser/SimpleSymbolTable.h +++ b/ExpressionParser/SimpleSymbolTable.h @@ -2,7 +2,6 @@ #define SIMPLESYMBOLTABLE_H #include -using std::vector; #include "SymbolTable.h" #include "SimpleSymbolTableEntry.h" @@ -10,7 +9,7 @@ using std::vector; class SimpleSymbolTable : public SymbolTable { public: - SimpleSymbolTable(string* symbols, int symbolCount, ValueProxy** valueProxies=0); + SimpleSymbolTable(std::string* symbols, int symbolCount, ValueProxy** valueProxies=0); /** * non-standard copy constructor -- transfers ownership of * implementation to new object; rhs will be unusable @@ -40,7 +39,7 @@ class SimpleSymbolTable : public SymbolTable private: SimpleSymbolTable & operator=(const SimpleSymbolTable &); - vector steArray; + std::vector steArray; }; #endif diff --git a/ExpressionParser/SimpleSymbolTableEntry.cpp b/ExpressionParser/SimpleSymbolTableEntry.cpp index 30e50cac3..96e3d5634 100644 --- a/ExpressionParser/SimpleSymbolTableEntry.cpp +++ b/ExpressionParser/SimpleSymbolTableEntry.cpp @@ -1,7 +1,7 @@ #include "SimpleSymbolTableEntry.h" #include "ExpressionException.h" -SimpleSymbolTableEntry::SimpleSymbolTableEntry(const string& nameValue, int indexVal, NameScope* namescopeVal, ValueProxy* proxyVal) +SimpleSymbolTableEntry::SimpleSymbolTableEntry(const std::string& nameValue, int indexVal, NameScope* namescopeVal, ValueProxy* proxyVal) : name(nameValue), index(indexVal), namescope(namescopeVal), valueProxy(proxyVal) { bConstant = false; @@ -28,7 +28,7 @@ int SimpleSymbolTableEntry::getIndex() { return index; } -string& SimpleSymbolTableEntry::getName() { +std::string& SimpleSymbolTableEntry::getName() { return name; } diff --git a/ExpressionParser/SimpleSymbolTableEntry.h b/ExpressionParser/SimpleSymbolTableEntry.h index ffebcd439..a66e23448 100644 --- a/ExpressionParser/SimpleSymbolTableEntry.h +++ b/ExpressionParser/SimpleSymbolTableEntry.h @@ -6,12 +6,12 @@ class SimpleSymbolTableEntry : public SymbolTableEntry { public: - SimpleSymbolTableEntry(const string& nameValue, int indexVal, NameScope* namescopeVal, ValueProxy* proxyVal); + SimpleSymbolTableEntry(const std::string& nameValue, int indexVal, NameScope* namescopeVal, ValueProxy* proxyVal); ~SimpleSymbolTableEntry(void); double getConstantValue(); VCell::Expression* getExpression(); int getIndex(); - string& getName(); + std::string& getName(); NameScope* getNameScope(); //VCUnitDefinition getUnitDefinition()=0; bool isConstant(); @@ -20,7 +20,7 @@ class SimpleSymbolTableEntry : public SymbolTableEntry ValueProxy* getValueProxy() { return valueProxy; }; private: - string name; + std::string name; int index; NameScope* namescope; bool bConstant; diff --git a/ExpressionParser/StackMachine.cpp b/ExpressionParser/StackMachine.cpp index a5f92d863..9b4839dd1 100644 --- a/ExpressionParser/StackMachine.cpp +++ b/ExpressionParser/StackMachine.cpp @@ -5,8 +5,8 @@ #include "Node.h" #include "StackMachine.h" -#include -#include +#include +#include #include "MathUtil.h" #include "DivideByZeroException.h" #include "FunctionDomainException.h" @@ -15,10 +15,6 @@ #include "ValueProxy.h" #include #include -using std::cout; -using std::endl; -using std::max; -using std::min; StackMachine::StackMachine(StackElement* arg_elements, int size) { elements = arg_elements; @@ -46,13 +42,13 @@ void StackMachine::showInstructions(){ StackElement *token = elements; for (int i=0; itype==TYPE_BZ){ - cout << "BZ " << token->branchOffset << endl; + std::cout << "BZ " << token->branchOffset << std::endl; }else if (token->type==TYPE_IDENTIFIER){ - cout << "PUSH " << "var[" << token->vectorIndex << "]" << endl; + std::cout << "PUSH " << "var[" << token->vectorIndex << "]" << std::endl; }else if (token->type==TYPE_FLOAT){ - cout << "PUSH " << token->value << endl; + std::cout << "PUSH " << token->value << std::endl; }else{ - cout << opCodes[token->type] << "()" << endl; + std::cout << opCodes[token->type] << "()" << std::endl; } } } @@ -155,7 +151,7 @@ double StackMachine::evaluate(double* values){ if (*tos < 0) { char problem[1000]; sprintf(problem, "sqrt(u) where u=%lf<0 is undefined", *tos); - throw FunctionDomainException(string(problem)); + throw FunctionDomainException(std::string(problem)); } *tos = sqrt(*tos); break; @@ -167,11 +163,11 @@ double StackMachine::evaluate(double* values){ if (*tos < 0.0 && (MathUtil::round(arg2) != arg2)) { char problem[1000]; sprintf(problem, "pow(u,v) and u=%lf<0 and v=%lf not an integer", *tos, arg2); - throw FunctionDomainException(string(problem)); + throw FunctionDomainException(std::string(problem)); } else if (*tos == 0.0 && arg2 < 0) { char problem[100]; sprintf(problem, "pow(u,v) and u=0 and v=%lf<0 divide by zero", arg2); - throw FunctionDomainException(string(problem)); + throw FunctionDomainException(std::string(problem)); } if (arg2 == 0.0) { *tos = 1.0; @@ -182,7 +178,7 @@ double StackMachine::evaluate(double* values){ if (MathUtil::double_infinity == -result || MathUtil::double_infinity == result || result != result) { char problem[1000]; sprintf(problem, "u^v evaluated to %lf, u=%lf, v=%lf", result, *tos, arg2); - throw FunctionDomainException(string(problem)); + throw FunctionDomainException(std::string(problem)); } *tos = result; } @@ -193,7 +189,7 @@ double StackMachine::evaluate(double* values){ } else if (*tos < 0.0) { char problem[1000]; sprintf(problem, "log(u) and u=%lf < 0.0 is undefined", *tos); - throw FunctionDomainException(string(problem)); + throw FunctionDomainException(std::string(problem)); } *tos = log(*tos); break; @@ -210,7 +206,7 @@ double StackMachine::evaluate(double* values){ if (fabs(*tos) > 1.0) { char problem[1000]; sprintf(problem, "asin(u) and u=%lf and |u|>1.0 undefined", *tos); - throw FunctionDomainException(string(problem)); + throw FunctionDomainException(std::string(problem)); } *tos = asin(*tos); break; @@ -218,7 +214,7 @@ double StackMachine::evaluate(double* values){ if (fabs(*tos) > 1.0) { char problem[1000]; sprintf(problem, "acos(u) and u=%lf and |u|>1.0 undefined", *tos); - throw FunctionDomainException(string(problem)); + throw FunctionDomainException(std::string(problem)); } *tos = acos(*tos); break; @@ -231,11 +227,11 @@ double StackMachine::evaluate(double* values){ break; case TYPE_MAX: // 29, pop 2 push 1 arg2 = *(tos--); - *tos = max(*tos, arg2); + *tos = std::max(*tos, arg2); break; case TYPE_MIN: // 30, pop 2 push 1 arg2 = *(tos--); - *tos = min(*tos, arg2); + *tos = std::min(*tos, arg2); break; case TYPE_CEIL: // 31, pop 1 push 1 *tos = ceil(*tos); @@ -249,7 +245,7 @@ double StackMachine::evaluate(double* values){ if (result == 0) { char problem[1000]; sprintf(problem, "csc(u)=1/sin(u) and sin(u)=0 and u=%lf", *tos); - throw FunctionDomainException(string(problem)); + throw FunctionDomainException(std::string(problem)); } *tos = 1/result; } @@ -260,7 +256,7 @@ double StackMachine::evaluate(double* values){ if (result == 0) { char problem[1000]; sprintf(problem, "cot(u)=1/tan(u) and tan(u)=0 and u=%lf", *tos); - throw FunctionDomainException(string(problem)); + throw FunctionDomainException(std::string(problem)); } *tos = 1/result; } @@ -271,7 +267,7 @@ double StackMachine::evaluate(double* values){ if (result == 0) { char problem[1000]; sprintf(problem, "sec(u)=1/cos(u) and cos(u)=0 and u=%lf", *tos); - throw FunctionDomainException(string(problem)); + throw FunctionDomainException(std::string(problem)); } *tos = 1/result; } @@ -280,7 +276,7 @@ double StackMachine::evaluate(double* values){ if (fabs(*tos) < 1.0){ char problem[1000]; sprintf(problem, "acsc(u) and -1= 1.0){ char problem[1000]; sprintf(problem, "atanh(u) and |u| >= 1.0, u=%lf", *tos); - throw FunctionDomainException(string(problem)); + throw FunctionDomainException(std::string(problem)); } *tos = MathUtil::atanh(*tos); break; @@ -353,7 +349,7 @@ double StackMachine::evaluate(double* values){ if (fabs(*tos) <= 1.0){ char problem[1000]; sprintf(problem, "acoth(u) and |u| <= 1.0, u=%lf", *tos); - throw FunctionDomainException(string(problem)); + throw FunctionDomainException(std::string(problem)); } *tos = MathUtil::acoth(*tos); break; @@ -361,7 +357,7 @@ double StackMachine::evaluate(double* values){ if (*tos <= 0.0 || *tos > 1.0){ char problem[1000]; sprintf(problem, "asech(u) and u <= 0.0 or u > 1.0, u=%lf", *tos); - throw FunctionDomainException(string(problem)); + throw FunctionDomainException(std::string(problem)); } *tos = MathUtil::asech(*tos); break; @@ -369,7 +365,7 @@ double StackMachine::evaluate(double* values){ if (*tos < 0.0 || (*tos-(int)*tos) != 0){ char problem[1000]; sprintf(problem, "factorial(u) and u=%lf < 0.0 or is not an integer", *tos); - throw FunctionDomainException(string(problem)); + throw FunctionDomainException(std::string(problem)); } *tos = MathUtil::factorial(*tos); break; diff --git a/ExpressionParser/SymbolTable.h b/ExpressionParser/SymbolTable.h index d85fe4aed..37ea81c44 100644 --- a/ExpressionParser/SymbolTable.h +++ b/ExpressionParser/SymbolTable.h @@ -2,7 +2,7 @@ #define SYMBOLTABLE_H #include -using std::string; + class SymbolTableEntry; diff --git a/ExpressionParser/SymbolTableEntry.h b/ExpressionParser/SymbolTableEntry.h index c39abc04c..e3b445843 100644 --- a/ExpressionParser/SymbolTableEntry.h +++ b/ExpressionParser/SymbolTableEntry.h @@ -2,7 +2,6 @@ #define SYMBOLTABLEENTRY_H #include -using std::string; class ValueProxy; namespace VCell { @@ -15,7 +14,7 @@ class SymbolTableEntry { virtual double getConstantValue()=0; virtual VCell::Expression* getExpression()=0; virtual int getIndex()=0; - virtual string& getName()=0; + virtual std::string& getName()=0; virtual NameScope* getNameScope()=0; //VCUnitDefinition getUnitDefinition()=0; virtual bool isConstant()=0; diff --git a/ExpressionParser/Token.h b/ExpressionParser/Token.h index 03a53fc37..e17c0def4 100644 --- a/ExpressionParser/Token.h +++ b/ExpressionParser/Token.h @@ -2,7 +2,6 @@ #define TOKEN_H #include -using std::string; class Token { @@ -12,7 +11,7 @@ class Token int kind; int beginLine, beginColumn, endLine, endColumn; - string image; + std::string image; Token* next; Token* specialToken; diff --git a/ExpressionParserTest/ExpressionParserTest.cpp b/ExpressionParserTest/ExpressionParserTest.cpp index 4cc934c97..4ac91cd4a 100644 --- a/ExpressionParserTest/ExpressionParserTest.cpp +++ b/ExpressionParserTest/ExpressionParserTest.cpp @@ -1,41 +1,38 @@ #include "Expression.h" #include "Exception.h" -using namespace VCell; -#include + #include -using std::cout; -using std::endl; -using std::cerr; -int main(int argc, char *argv[]) { - - vector expStrs; - expStrs.push_back(string("log(0)")); - expStrs.push_back(string("(1<(2*(1<2)))*5+(1<(2*(2<1)))*5")); - expStrs.push_back(string("(1<2)*3+5*log(0)*(0>0)+2")); - expStrs.push_back(string("(1<2)*1*2+3*5+4+(1<1)")); - expStrs.push_back(string("(1<3)*(1 + +int main() { + std::vector expStrs; + expStrs.push_back(std::string("log(0)")); + expStrs.push_back(std::string("(1<(2*(1<2)))*5+(1<(2*(2<1)))*5")); + expStrs.push_back(std::string("(1<2)*3+5*log(0)*(0>0)+2")); + expStrs.push_back(std::string("(1<2)*1*2+3*5+4+(1<1)")); + expStrs.push_back(std::string("(1<3)*(1 " << expression.evaluateConstant() << endl; - cout << "tree eval (constant) ==> " << expression.evaluateConstantTree() << endl; - cout << "stack machine (vector) ==> " << expression.evaluateVector(values) << endl; - cout << "tree eval (vector) ==> " << expression.evaluateVectorTree(values) << endl; + std::cout << "stack machine (constant) ==> " << expression.evaluateConstant() << std::endl; + std::cout << "tree eval (constant) ==> " << expression.evaluateConstantTree() << std::endl; + std::cout << "stack machine (vector) ==> " << expression.evaluateVector(values) << std::endl; + std::cout << "tree eval (vector) ==> " << expression.evaluateVectorTree(values) << std::endl; } catch (const char* ex) { - cerr << "ExpressionParserTest failed : " << ex << endl; - } catch (string& ex) { - cerr << "ExpressionParserTest failed : " << ex << endl; - } catch (Exception& ex) { - cerr << "ExpressionParserTest failed : " << ex.getMessage() << endl; + std::cerr << "ExpressionParserTest failed : " << ex << std::endl; + } catch (std::string& ex) { + std::cerr << "ExpressionParserTest failed : " << ex << std::endl; + } catch (VCell::Exception& ex) { + std::cerr << "ExpressionParserTest failed : " << ex.getMessage() << std::endl; } catch (...) { - cerr << "ExpressionParserTest failed : unknown error." << endl; + std::cerr << "ExpressionParserTest failed : unknown error." << std::endl; } } } diff --git a/IDAWin/OdeResultSet.cpp b/IDAWin/OdeResultSet.cpp index 9ca5758d0..376e80d0a 100644 --- a/IDAWin/OdeResultSet.cpp +++ b/IDAWin/OdeResultSet.cpp @@ -1,190 +1,170 @@ +#include #include "OdeResultSet.h" -#include "Expression.h" -#include "SymbolTable.h" #include "Exception.h" +#include "Expression.h" -#include +OdeResultSet::OdeResultSet(): columnWeights(nullptr), rowData(nullptr), numRowsAllocated(0), numRowsUsed(0), numFunctionColumns(0), numDataColumns(0) +{} -OdeResultSet::OdeResultSet() -{ - columnWeights = NULL; - numFunctionColumns = 0; - numRowsAllocated = 0; - numRowsUsed = 0; - numDataColumns = 0; - rowData = 0; -} - -OdeResultSet::~OdeResultSet() -{ - for (int i = 0; i < (int)columns.size(); i ++) { - delete columns[i].expression; - } - columns.clear(); +OdeResultSet::~OdeResultSet(){ + for (const Column& column : this->columns) delete column.expression; + this->columns.clear(); delete[] rowData; delete[] columnWeights; } -void OdeResultSet::addColumn(const string& aColumn) { - if (numRowsAllocated != 0) { - throw VCell::Exception("Can't add column when rowData is not empty"); - } - columns.push_back(Column(aColumn, 0)); - numDataColumns ++; +void OdeResultSet::addColumn(const std::string& aColumn) { + if (0 != this->numRowsAllocated) throw VCell::Exception("Can't add column when rowData is not empty"); + this->columns.emplace_back(aColumn, nullptr); + this->numDataColumns++; } -void OdeResultSet::bindFunctionExpression(SymbolTable* symbolTable) { - for (int i = 0; i < (int)columns.size(); i ++) { - if (columns[i].expression != 0) { - columns[i].expression->bindExpression(symbolTable); - } +void OdeResultSet::bindFunctionExpression(SymbolTable* symbolTable) const { + for (const Column& column : this->columns) { + if (column.expression == nullptr) continue; + column.expression->bindExpression(symbolTable); } } -void OdeResultSet::addFunctionColumn(const string& aColumn, const string& exp) { - columns.push_back(Column(aColumn, new VCell::Expression(exp))); - numFunctionColumns ++; +void OdeResultSet::addFunctionColumn(const std::string& aColumn, const std::string& columnExpression) { + this->columns.push_back(Column(aColumn, new VCell::Expression(columnExpression))); + this->numFunctionColumns ++; } -void OdeResultSet::addRow(double* aRow) { - if (numRowsAllocated == 0) { - numRowsAllocated = 500; - rowData = new double[numRowsAllocated * numDataColumns]; - memset(rowData, 0, numRowsAllocated * numDataColumns * sizeof(double)); - } else if (numRowsAllocated == numRowsUsed) { - int oldNumRowsAllocated = numRowsAllocated; - double* oldRowData = rowData; - numRowsAllocated += 500; - rowData = new double[numRowsAllocated * numDataColumns]; - memset(rowData, 0, numRowsAllocated * numDataColumns * sizeof(double)); - memcpy(rowData, oldRowData, oldNumRowsAllocated * numDataColumns * sizeof(double)); +void OdeResultSet::addRow(const double* aRow) { + if (this->numRowsAllocated == 0) { + this->numRowsAllocated = 500; + this->rowData = new double[this->numRowsAllocated * this->numDataColumns]; + memset(this->rowData, 0, this->numRowsAllocated * this->numDataColumns * sizeof(double)); + } else if (this->numRowsAllocated == this->numRowsUsed) { + const int oldNumRowsAllocated = this->numRowsAllocated; + const double* oldRowData = this->rowData; + this->numRowsAllocated += 500; + this->rowData = new double[this->numRowsAllocated * this->numDataColumns]; + memset(this->rowData, 0, this->numRowsAllocated * this->numDataColumns * sizeof(double)); + memcpy(this->rowData, oldRowData, oldNumRowsAllocated * this->numDataColumns * sizeof(double)); delete[] oldRowData; } - int index = numRowsUsed * numDataColumns; - for (int i = 0; i < numDataColumns; i ++, index ++) { - rowData[index] = aRow[i]; + int index = this->numRowsUsed * this->numDataColumns; + for (int i = 0; i < this->numDataColumns; i ++, index ++) { + this->rowData[index] = aRow[i]; } - numRowsUsed ++; + this->numRowsUsed++; } -void OdeResultSet::setColumnWeights(double* weights){ - delete[] columnWeights; - columnWeights = new double[columns.size()]; - memcpy(columnWeights, weights, columns.size() * sizeof(double)); +void OdeResultSet::setColumnWeights(const double* weights){ + delete[] this->columnWeights; + this->columnWeights = new double[this->columns.size()]; + memcpy(this->columnWeights, weights, this->columns.size() * sizeof(double)); } -double* OdeResultSet::getRowData(int index) { - if (index >= numRowsUsed) { +double* OdeResultSet::getRowData(const int index) { + if (index >= this->numRowsUsed) { throw VCell::Exception("OdeResultSet::getRowData(int index), row index is out of bounds"); } - return rowData + index * numDataColumns; + return this->rowData + index * this->numDataColumns; } void OdeResultSet::clearData() { - numRowsUsed = 0; - memset(rowData, 0, numRowsAllocated * numDataColumns * sizeof(double)); + this->numRowsUsed = 0; + memset(this->rowData, 0, this->numRowsAllocated * this->numDataColumns * sizeof(double)); } -int OdeResultSet::findColumn(const string& aColumn) { +int OdeResultSet::findColumn(const std::string& aColumn) const { int columnIndex = 0; - for (vector::iterator iter = columns.begin(); iter < columns.end(); iter++) { - if ((*iter).name == aColumn) { - break; - } - columnIndex ++; - } - if (columnIndex == columns.size()) { - columnIndex = -1; + for (const Column& column : this->columns) { + if (column.name == aColumn) break; + columnIndex++; } + if (columnIndex == this->columns.size()) columnIndex = -1; return columnIndex; } double OdeResultSet::getColumnWeight(int index) { - if (index >= (int)columns.size()) { - throw "OdeResultSet::getColumnWeight(int index), column index is out of bounds"; + if (index >= (int)this->columns.size()) { + throw std::out_of_range("OdeResultSet::getColumnWeight(int index), column index is out of bounds"); } return columnWeights[index]; } -int OdeResultSet::getNumColumns() { - return (int)columns.size(); +int OdeResultSet::getNumColumns() const { + return static_cast(this->columns.size()); } -string& OdeResultSet::getColumnName(int index) { - if (index >= (int)columns.size()) { - throw "OdeResultSet::getColumnName(int index), column index is out of bounds"; +std::string& OdeResultSet::getColumnName(const int index) { + if (index >= static_cast(this->columns.size())) { + throw std::out_of_range("OdeResultSet::getColumnName(int index), column index is out of bounds"); } - return columns[index].name; + return this->columns[index].name; } -int OdeResultSet::getNumRows() { - return numRowsUsed; +int OdeResultSet::getNumRows() const { + return this->numRowsUsed; } -VCell::Expression* OdeResultSet::getColumnFunctionExpression(int columnIndex) { - if (columnIndex >= (int)columns.size()) { - throw "OdeResultSet::getColumnFunctionExpression(), column index is out of bounds"; +VCell::Expression* OdeResultSet::getColumnFunctionExpression(const int columnIndex) const { + if (columnIndex >= static_cast(this->columns.size())) { + throw std::out_of_range("OdeResultSet::getColumnFunctionExpression(), column index is out of bounds"); } - return columns[columnIndex].expression; + return this->columns[columnIndex].expression; } -void OdeResultSet::getColumnData(int columnIndex, int numParams, double* paramValues, double* colData) { - int numCols = getNumColumns(); - if (columnIndex < 0 || columnIndex >= numCols){ - throw "OdeResultSet::getColumnData(int columnIndex), columnIndex out of bounds"; +void OdeResultSet::getColumnData(const int index, const int numParams, const double* paramValues, double* colData) const { + if (index < 0 || index >= this->getNumColumns()){ + throw std::out_of_range("OdeResultSet::getColumnData(int columnIndex), columnIndex out of bounds"); } - if (columns[columnIndex].expression == 0) { - for (int i = 0; i < numRowsUsed; i++){ - colData[i] = rowData[i * numDataColumns + columnIndex]; + if (this->columns[index].expression == nullptr) { + for (int i = 0; i < this->numRowsUsed; i++){ + colData[i] = this->rowData[i * this->numDataColumns + index]; } } else { // Function - double* values = new double[numDataColumns + numParams]; - memcpy(values + numDataColumns, paramValues, numParams * sizeof(double)); - for (int i = 0; i < numRowsUsed; i++){ - memcpy(values, rowData + i * numDataColumns, numDataColumns * sizeof(double)); - colData[i] = columns[columnIndex].expression->evaluateVector(values); + auto* values = new double[this->numDataColumns + numParams]; + memcpy(values + this->numDataColumns, paramValues, numParams * sizeof(double)); + for (int i = 0; i < this->numRowsUsed; i++){ + memcpy(values, this->rowData + i * this->numDataColumns, this->numDataColumns * sizeof(double)); + colData[i] = this->columns[index].expression->evaluateVector(values); } delete[] values; } } -void OdeResultSet::copyInto(OdeResultSet* otherOdeResultSet) { +void OdeResultSet::copyInto(OdeResultSet* otherOdeResultSet) const { // columns - if (otherOdeResultSet->columns.size() != columns.size()) { - for (int i = 0; i < (int)otherOdeResultSet->columns.size(); i ++) { - delete otherOdeResultSet->columns[i].expression; + if (otherOdeResultSet->columns.size() != this->columns.size()) { + for (const Column& column : otherOdeResultSet->columns) { + delete column.expression; } otherOdeResultSet->columns.clear(); - for (int i = 0; i < (int)columns.size(); i ++) { - if (columns[i].expression == 0) { - otherOdeResultSet->addColumn(columns[i].name); + for (const Column& column : this->columns) { + if (nullptr == column.expression) { + otherOdeResultSet->addColumn(column.name); } else { - otherOdeResultSet->addFunctionColumn(columns[i].name, columns[i].expression->infix()); + otherOdeResultSet->addFunctionColumn(column.name, column.expression->infix()); } } - if (columnWeights != 0) { - otherOdeResultSet->setColumnWeights(columnWeights); + if (nullptr != this->columnWeights) { + otherOdeResultSet->setColumnWeights(this->columnWeights); } } // rows - if (otherOdeResultSet->numRowsAllocated != numRowsAllocated) { + if (otherOdeResultSet->numRowsAllocated != this->numRowsAllocated) { delete[] otherOdeResultSet->rowData; - otherOdeResultSet->rowData = new double[numRowsAllocated * numDataColumns]; - otherOdeResultSet->numRowsAllocated = numRowsAllocated; + otherOdeResultSet->rowData = new double[this->numRowsAllocated * this->numDataColumns]; + otherOdeResultSet->numRowsAllocated = this->numRowsAllocated; } - otherOdeResultSet->numRowsUsed = numRowsUsed; - memcpy(otherOdeResultSet->rowData, rowData, numRowsAllocated * numDataColumns * sizeof(double)); + otherOdeResultSet->numRowsUsed = this->numRowsUsed; + memcpy(otherOdeResultSet->rowData, this->rowData, this->numRowsAllocated * this->numDataColumns * sizeof(double)); } void OdeResultSet::addEmptyRows(int numRowsToAdd) { - if (numRowsAllocated < numRowsUsed + numRowsToAdd) { - int oldNumRowsAllocated = numRowsAllocated; - double* oldRowData = rowData; - numRowsAllocated = numRowsUsed + numRowsToAdd; - rowData = new double[numRowsAllocated * numDataColumns]; - memset(rowData, 0, numRowsAllocated * numDataColumns * sizeof(double)); - memcpy(rowData, oldRowData, oldNumRowsAllocated * numDataColumns * sizeof(double)); + if (this->numRowsAllocated < this->numRowsUsed + numRowsToAdd) { + int oldNumRowsAllocated = this->numRowsAllocated; + double* oldRowData = this->rowData; + this->numRowsAllocated = this->numRowsUsed + numRowsToAdd; + this->rowData = new double[this->numRowsAllocated * this->numDataColumns]; + memset(this->rowData, 0, this->numRowsAllocated * this->numDataColumns * sizeof(double)); + memcpy(this->rowData, oldRowData, oldNumRowsAllocated * this->numDataColumns * sizeof(double)); delete[] oldRowData; } - numRowsUsed += numRowsToAdd; + this->numRowsUsed += numRowsToAdd; } diff --git a/IDAWin/OdeResultSet.h b/IDAWin/OdeResultSet.h index 08eb0fcfe..42a8505e7 100644 --- a/IDAWin/OdeResultSet.h +++ b/IDAWin/OdeResultSet.h @@ -2,67 +2,84 @@ #define ODERESULTSET_H #include +#include #include namespace VCell { class Expression; } + class SymbolTable; struct Column { std::string name; - VCell::Expression* expression; - - Column(std::string arg_name, VCell::Expression* exp) { - name = arg_name; - expression = exp; + VCell::Expression *expression; + + Column(std::string arg_name, VCell::Expression *exp) { + this->name = std::move(arg_name); + this->expression = exp; } }; -class OdeResultSet -{ -public: - OdeResultSet(); - ~OdeResultSet(); - void addColumn(const std::string& aColumn); - void addFunctionColumn(const std::string& aColumn, const std::string& columnExpression); - void addRow(double* aRow); - void setColumnWeights(double* weights); - - void bindFunctionExpression(SymbolTable* symbolTable); - - double* getRowData(int index); - const double* getRowData() { - return rowData; - } +class OdeResultSet { + public: + OdeResultSet(); + + ~OdeResultSet(); + + // Column Methods + [[nodiscard]] int getNumColumns() const; + + std::vector getColumns() { return this->columns; } + + void addColumn(const std::string &aColumn); + + [[nodiscard]] int findColumn(const std::string &aColumn) const; + + std::string &getColumnName(int index); + + double getColumnWeight(int index); + + void setColumnWeights(const double *weights); + + void getColumnData(int index, int numParams, const double *paramValues, double *colData) const; + + // Function Methods + void addFunctionColumn(const std::string &aColumn, const std::string &columnExpression); + + [[nodiscard]] int getNumFunctionColumns() const { return this->numFunctionColumns; } + [[nodiscard]] int getNumDataColumns() const { return this->numDataColumns; } + + [[nodiscard]] VCell::Expression *getColumnFunctionExpression(int columnIndex) const; + + void bindFunctionExpression(SymbolTable *symbolTable) const; + + // Row Methods + [[nodiscard]] int getNumRows() const; + + void addRow(const double *aRow); + + double *getRowData(int index); + + [[nodiscard]] double *getAllRowData() const { return this->rowData; } + + void addEmptyRows(int numRowsToAdd); + + // Misc. + void copyInto(OdeResultSet *otherOdeResultSet) const; + + void clearData(); - int findColumn(const std::string& aColumn); - double getColumnWeight(int index); - std::string& getColumnName(int index); - void getColumnData(int index, int numParams, double* paramValues, double* colData); - - int getNumColumns(); - int getNumRows(); - int getNumFunctionColumns() { return numFunctionColumns; } - int getNumDataColumns() { return numDataColumns; } - std::vector getColumns(){ return columns;} - - VCell::Expression* getColumnFunctionExpression(int columnIndex); - void clearData(); - - void copyInto(OdeResultSet* otherOdeResultSet); - void addEmptyRows(int numRowsToAdd); - -private: - // 0 : t - // 1 ~ N : variable names; - std::vector columns; - double* columnWeights; - double* rowData; - int numRowsAllocated; - int numRowsUsed; - int numFunctionColumns; - int numDataColumns; + private: + // 0 : t + // 1 ~ N : variable names; + std::vector columns; + double *columnWeights; + double *rowData; + int numRowsAllocated; + int numRowsUsed; + int numFunctionColumns; + int numDataColumns; }; #endif diff --git a/IDAWin/StoppedByUserException.cpp b/IDAWin/StoppedByUserException.cpp index 6bc89b05a..e1ef6787c 100644 --- a/IDAWin/StoppedByUserException.cpp +++ b/IDAWin/StoppedByUserException.cpp @@ -1,9 +1,6 @@ #include "StoppedByUserException.h" -StoppedByUserException::StoppedByUserException(string msg) : Exception("StoppedByUserException: " + msg) -{ -} +StoppedByUserException::StoppedByUserException(const string& msg) + : Exception("StoppedByUserException: " + msg){} -StoppedByUserException::~StoppedByUserException(void) throw( ) -{ -} +StoppedByUserException::~StoppedByUserException() noexcept = default; diff --git a/IDAWin/StoppedByUserException.h b/IDAWin/StoppedByUserException.h index 813db45b3..de2f56c6e 100644 --- a/IDAWin/StoppedByUserException.h +++ b/IDAWin/StoppedByUserException.h @@ -3,11 +3,10 @@ #include -class StoppedByUserException : public VCell::Exception -{ +class StoppedByUserException final : public VCell::Exception { public: - StoppedByUserException(string msg); - ~StoppedByUserException(void) throw( ); + explicit StoppedByUserException(const string& msg); + ~StoppedByUserException() noexcept override; }; #endif diff --git a/IDAWin/VCellCVodeSolver.cpp b/IDAWin/VCellCVodeSolver.cpp index b36e7e5ad..f81ed0a59 100644 --- a/IDAWin/VCellCVodeSolver.cpp +++ b/IDAWin/VCellCVodeSolver.cpp @@ -1,17 +1,14 @@ #include "VCellCVodeSolver.h" #include "OdeResultSet.h" #include -#include #include -#include +#include #include #include #include -#include "StoppedByUserException.h" -#include #include #include -using std::stringstream; +#include #ifdef USE_MESSAGING #include @@ -122,17 +119,17 @@ void VCellCVodeSolver::throwCVodeErrorMessage(int returnCode) { throw "CV_LSOLVE_FAIL: the linear solver's solve routine failed in an unrecoverable manner"; } case CV_REPTD_RHSFUNC_ERR: { - stringstream ss; + std::stringstream ss; ss << "CV_REPTD_RHSFUNC_ERR: repeated recoverable right-hand side function errors : " << recoverableErrMsg; throw ss.str(); } case CV_UNREC_RHSFUNC_ERR:{ - stringstream ss; + std::stringstream ss; ss << "CV_UNREC_RHSFUNC_ERR: the right-hand side failed in a recoverable manner, but no recovery is possible : " << recoverableErrMsg; throw ss.str(); } case CV_FIRST_RHSFUNC_ERR: { - stringstream ss; + std::stringstream ss; ss << "CV_FIRST_RHSFUNC_ERR: The right-hand side routine failed at the first call : " << recoverableErrMsg; throw ss.str(); } @@ -178,8 +175,8 @@ Input format: */ void VCellCVodeSolver::readEquations(std::istream& inputstream) { try { - string name; - string exp; + std::string name; + std::string exp; rateExpressions = new Expression*[NEQ]; @@ -192,7 +189,7 @@ void VCellCVodeSolver::readEquations(std::istream& inputstream) { try { initialConditionExpressions[i] = readExpression(inputstream); } catch (VCell::Exception& ex) { - throw VCell::Exception(string("Initial condition expression for [") + variableNames[i] + "] " + ex.getMessage()); + throw VCell::Exception(std::string("Initial condition expression for [") + variableNames[i] + "] " + ex.getMessage()); } // RATE @@ -200,13 +197,13 @@ void VCellCVodeSolver::readEquations(std::istream& inputstream) { try { rateExpressions[i] = readExpression(inputstream); } catch (VCell::Exception& ex) { - throw VCell::Exception(string("Rate expression for [") + variableNames[i] + "] " + ex.getMessage()); + throw VCell::Exception(std::string("Rate expression for [") + variableNames[i] + "] " + ex.getMessage()); } } } catch (char* ex) { - throw VCell::Exception(string("VCellCVodeSolver::readInput() : ") + ex); + throw VCell::Exception(std::string("VCellCVodeSolver::readInput() : ") + ex); } catch (VCell::Exception& ex) { - throw VCell::Exception(string("VCellCVodeSolver::readInput() : ") + ex.getMessage()); + throw VCell::Exception(std::string("VCellCVodeSolver::readInput() : ") + ex.getMessage()); } catch (...) { throw "VCellCVodeSolver::readInput() : caught unknown exception"; } @@ -230,15 +227,15 @@ int VCellCVodeSolver::RHS (realtype t, N_Vector y, N_Vector r) { } recoverableErrMsg = ""; return 0; - }catch (DivideByZeroException e){ + }catch (DivideByZeroException& e){ std::cout << "failed to evaluate residual: " << e.getMessage() << std::endl; recoverableErrMsg = e.getMessage(); return 1; - }catch (FunctionDomainException e){ + }catch (FunctionDomainException& e){ std::cout << "failed to evaluate residual: " << e.getMessage() << std::endl; recoverableErrMsg = e.getMessage(); return 1; - }catch (FunctionRangeException e){ + }catch (FunctionRangeException& e){ std::cout << "failed to evaluate residual: " << e.getMessage() << std::endl; recoverableErrMsg = e.getMessage(); return 1; @@ -260,14 +257,11 @@ int VCellCVodeSolver::RootFn_callback(realtype t, N_Vector y, realtype *gout, vo } void VCellCVodeSolver::solve(double* paramValues, bool bPrintProgress, FILE* outputFile, void (*checkStopRequested)(double, long)) { - if (checkStopRequested != 0) { - checkStopRequested(STARTING_TIME, 0); - } - + if (checkStopRequested != nullptr) checkStopRequested(STARTING_TIME, 0); writeFileHeader(outputFile); // clear data in result set before solving - odeResultSet->clearData(); + this->odeResultSet->clearData(); // copy parameter values to the end of values, these will stay the same during solving memset(values, 0, (NEQ + 1) * sizeof(double)); @@ -301,9 +295,9 @@ void VCellCVodeSolver::initCVode(double* paramValues) { void VCellCVodeSolver::reInit(double t) { int flag = 0; - if (solver == 0) { + if (solver == nullptr) { solver = CVodeCreate(CV_BDF, CV_NEWTON); - if (solver == 0) { + if (solver == nullptr) { throw "VCellCVodeSolver:: Out of memory"; } flag = CVodeMalloc(solver, RHS_callback, t, y, ToleranceType, RelativeTolerance, &AbsoluteTolerance); @@ -434,10 +428,9 @@ void VCellCVodeSolver::cvodeSolve(bool bPrintProgress, FILE* outputFile, void (* if (returnCode == CV_ROOT_RETURN || iterationCount % keepEvery == 0 || Time >= ENDING_TIME){ outputCount++; if (outputCount * (NEQ + 1) * bytesPerSample > MaxFileSizeBytes){ - /* if more than one gigabyte, then fail */ - char msg[100]; - sprintf(msg, "output exceeded maximum %d bytes", MaxFileSizeBytes); - throw VCell::Exception(msg); + /* if more than one gigabyte, then fail */ + const std::string msg{"output exceeded maximum " + std::to_string(MaxFileSizeBytes) + " bytes"}; + throw VCell::Exception(msg.c_str()); } writeData(Time, outputFile); if (bPrintProgress) { diff --git a/IDAWin/VCellCVodeSolver.h b/IDAWin/VCellCVodeSolver.h index ebb4d3f76..f9cceb84a 100644 --- a/IDAWin/VCellCVodeSolver.h +++ b/IDAWin/VCellCVodeSolver.h @@ -2,19 +2,20 @@ #define VCELLCVODESOLVER_H #include "VCellSundialsSolver.h" +#include class VCellCVodeSolver : public VCellSundialsSolver { public: VCellCVodeSolver(); - ~VCellCVodeSolver(); + ~VCellCVodeSolver() override; - void solve(double* paramValues=0, bool bPrintProgress=false, FILE* outputFile=0, void (*checkStopRequested)(double, long)=0); + void solve(double* paramValues=nullptr, bool bPrintProgress=false, FILE* outputFile=nullptr, void (*checkStopRequested)(double, long)=nullptr) override; double RHS(double* allValues, int equationIndex); protected: - void readEquations(std::istream& inputstream); - void initialize(); - string getSolverName() { return "CVODE"; } + void readEquations(std::istream& inputstream) override; + void initialize() override; + std::string getSolverName() override { return "CVODE"; } private: VCell::Expression** rateExpressions; @@ -52,8 +53,8 @@ class VCellCVodeSolver : public VCellSundialsSolver { void checkCVodeFlag(int flag); void reInit(realtype t); - bool fixInitialDiscontinuities(double t); - void updateTandVariableValues(realtype t, N_Vector y); + bool fixInitialDiscontinuities(double t) override; + void updateTandVariableValues(realtype t, N_Vector y) override; void onCVodeReturn(realtype Time, int returnCode); diff --git a/IDAWin/VCellIDASolver.cpp b/IDAWin/VCellIDASolver.cpp index bd0e5de63..4a4fce35c 100644 --- a/IDAWin/VCellIDASolver.cpp +++ b/IDAWin/VCellIDASolver.cpp @@ -9,13 +9,14 @@ #include #include #include -using std::stringstream; + #ifdef USE_MESSAGING #include #endif #include +#include #include #include //#include @@ -158,7 +159,7 @@ void VCellIDASolver::throwIDAErrorMessage(int returnCode) { throw "IDA_CONSTR_FAIL: The inequality constraints were violated, and the solver was unable to recover."; } case IDA_REP_RES_ERR:{ - stringstream ss; + std::stringstream ss; ss << "IDA_REP_RES_ERR: The user's residual function repeatedly returned a recoverable error flag, but the solver was unable to recover. " << recoverableErrMsg; throw ss.str(); } @@ -172,7 +173,7 @@ void VCellIDASolver::throwIDAErrorMessage(int returnCode) { throw "IDA_BAD_EWT: Some component of the error weight vector is zero (illegal)."; } case IDA_FIRST_RES_FAIL:{ - stringstream ss; + std::stringstream ss; ss << "IDA_FIRST_RES_FAIL: The user's residual function returned a recoverable error flag on the first call, but IDA was unable to recover. " << recoverableErrMsg; throw ss.str(); } @@ -294,8 +295,8 @@ Input format: void VCellIDASolver::readEquations(std::istream& inputstream) { try { - string token; - string exp; + std::string token; + std::string exp; // // test whether it is a "VAR" block (i.e. whether VAR) @@ -317,7 +318,7 @@ void VCellIDASolver::readEquations(std::istream& inputstream) { try { initialConditionExpressions[i] = readExpression(inputstream); } catch (VCell::Exception& ex) { - throw VCell::Exception(string("Initial condition expression for [") + variableNames[i] + "] " + ex.getMessage()); + throw VCell::Exception(std::string("Initial condition expression for [") + variableNames[i] + "] " + ex.getMessage()); } } @@ -373,15 +374,15 @@ void VCellIDASolver::readEquations(std::istream& inputstream) { try { rhsExpressions[i] = readExpression(inputstream); } catch (VCell::Exception& ex) { - stringstream ss; + std::stringstream ss; ss << "RHS[" << i << "] " << ex.getMessage(); throw VCell::Exception(ss.str()); } } } catch (const char* ex) { - throw VCell::Exception(string("VCellIDASolver::readInput() : ") + ex); + throw VCell::Exception(std::string("VCellIDASolver::readInput() : ") + ex); } catch (VCell::Exception& ex) { - throw VCell::Exception(string("VCellIDASolver::readInput() : ") + ex.getMessage()); + throw VCell::Exception(std::string("VCellIDASolver::readInput() : ") + ex.getMessage()); } catch (...) { throw VCell::Exception("VCellIDASolver::readInput() caught unknown exception"); } diff --git a/IDAWin/VCellIDASolver.h b/IDAWin/VCellIDASolver.h index 2d4bae607..459701003 100644 --- a/IDAWin/VCellIDASolver.h +++ b/IDAWin/VCellIDASolver.h @@ -2,6 +2,7 @@ #define VCELLIDASOLVER_H #include "VCellSundialsSolver.h" +#include class VCellIDASolver : public VCellSundialsSolver { public: @@ -14,7 +15,7 @@ class VCellIDASolver : public VCellSundialsSolver { void updateTempRowData(double currTime); void readEquations(std::istream& inputstream); void initialize(); - string getSolverName() { return "IDA"; } + std::string getSolverName() { return "IDA"; } private: VCell::Expression** rhsExpressions; // can be rate expression in ODE case or RHS expression in DAE case diff --git a/IDAWin/VCellSundialsSolver.cpp b/IDAWin/VCellSundialsSolver.cpp index bc40c3692..06257a695 100644 --- a/IDAWin/VCellSundialsSolver.cpp +++ b/IDAWin/VCellSundialsSolver.cpp @@ -2,10 +2,12 @@ #include "StoppedByUserException.h" #include "VCellSundialsSolver.h" #include "OdeResultSet.h" -#include -#include +#include +#include +#include #include -using std::stringstream; +#include + #include @@ -13,28 +15,20 @@ using std::stringstream; #include #endif -static void trimString(string& str) -{ - string::size_type pos = str.find_last_not_of(" \r\n"); - if(pos != string::npos) { +static void trimString(std::string &str) { + std::string::size_type pos = str.find_last_not_of(" \r\n"); + if (pos != std::string::npos) { str.erase(pos + 1); pos = str.find_first_not_of(" \r\n"); - if(pos != string::npos) { - str.erase(0, pos); - } - } - else { - str.erase(str.begin(), str.end()); - } + if (pos != std::string::npos) { str.erase(0, pos); } + } else { str.erase(str.begin(), str.end()); } } -VCell::Expression* VCellSundialsSolver::readExpression(std::istream& inputstream) { - string exp; +VCell::Expression *VCellSundialsSolver::readExpression(std::istream &inputstream) { + std::string exp; getline(inputstream, exp); trimString(exp); - if (*(exp.end() - 1) != ';') { - throw VCell::Exception(BAD_EXPRESSION_MSG); - } + if (*(exp.end() - 1) != ';') { throw VCell::Exception(BAD_EXPRESSION_MSG); } return new VCell::Expression(exp); } @@ -42,57 +36,53 @@ VCellSundialsSolver::VCellSundialsSolver() { NEQ = 0; NPARAM = 0; STARTING_TIME = 0.0; - ENDING_TIME = 0.0; + ENDING_TIME = 0.0; RelativeTolerance = 0.0; AbsoluteTolerance = 0.0; keepEvery = 0; - maxTimeStep = 0.0; + maxTimeStep = 0.0; - solver = 0; - initialConditionSymbolTable = 0; - initialConditionExpressions = 0; - values = 0; - tempRowData = 0; + solver = nullptr; + initialConditionSymbolTable = nullptr; + initialConditionExpressions = nullptr; + values = nullptr; + tempRowData = nullptr; - variableNames = 0; - paramNames = 0; - allSymbols = 0; + variableNames = nullptr; + paramNames = nullptr; + allSymbols = nullptr; numAllSymbols = 0; - defaultSymbolTable = 0; + defaultSymbolTable = nullptr; numDiscontinuities = 0; - odeDiscontinuities = 0; - discontinuityValues = 0; - discontinuitySymbolTable = 0; - rootsFound = 0; + odeDiscontinuities = nullptr; + discontinuityValues = nullptr; + discontinuitySymbolTable = nullptr; + rootsFound = nullptr; - odeResultSet = new OdeResultSet(); - y = 0; + odeResultSet = new OdeResultSet(); + y = nullptr; - events = 0; + events = nullptr; numEvents = 0; } VCellSundialsSolver::~VCellSundialsSolver() { N_VDestroy_Serial(y); - for (int i = 0; i < NEQ; i ++) { - delete initialConditionExpressions[i]; - } + for (int i = 0; i < NEQ; i++) { delete initialConditionExpressions[i]; } delete[] initialConditionExpressions; delete[] variableNames; delete[] paramNames; delete[] allSymbols; delete defaultSymbolTable; - for (int i = 0; i < numDiscontinuities; i ++) { - delete odeDiscontinuities[i]; - } + for (int i = 0; i < numDiscontinuities; i++) { delete odeDiscontinuities[i]; } delete[] odeDiscontinuities; delete[] rootsFound; delete[] discontinuityValues; delete discontinuitySymbolTable; - + delete[] values; delete[] tempRowData; delete initialConditionSymbolTable; @@ -100,37 +90,36 @@ VCellSundialsSolver::~VCellSundialsSolver() { outputTimes.clear(); - for (int i = 0; i < numEvents; i ++) { - delete events[i]; - } + for (int i = 0; i < numEvents; i++) { delete events[i]; } delete[] events; + + for (EventExecution* elem : eventExeList) { + delete elem; + } + eventExeList.clear(); } void VCellSundialsSolver::updateTempRowData(double currTime) { - tempRowData[0] = currTime; - for (int i = 0; i < NEQ; i++) { - tempRowData[i+1] = NV_Ith_S(y,i); - } + tempRowData[0] = currTime; + for (int i = 0; i < NEQ; i++) { tempRowData[i + 1] = NV_Ith_S(y, i); } } -void VCellSundialsSolver::writeData(double currTime, FILE* outputFile) { +void VCellSundialsSolver::writeData(double currTime, FILE *outputFile) { updateTempRowData(currTime); odeResultSet->addRow(tempRowData); writeFileData(outputFile); } -void VCellSundialsSolver::writeFileData(FILE* outputFile) { - if (outputFile != 0) { - fprintf(outputFile, "%0.17E", tempRowData[0]); - for (int i = 1; i < NEQ+1; i++) { - fprintf(outputFile, "\t%0.17E", tempRowData[i]); - } +void VCellSundialsSolver::writeFileData(FILE *outputFile) { + if (outputFile != nullptr) { + fprintf(outputFile, "%0.17E", tempRowData[0]); + for (int i = 1; i < NEQ + 1; i++) { fprintf(outputFile, "\t%0.17E", tempRowData[i]); } fprintf(outputFile, "\n"); } } -void VCellSundialsSolver::writeFileHeader(FILE* outputFile) { - if (outputFile != 0) { +void VCellSundialsSolver::writeFileHeader(FILE *outputFile) { + if (outputFile != nullptr) { // Print header... for (int i = 0; i < odeResultSet->getNumColumns(); i++) { fprintf(outputFile, "%s:", odeResultSet->getColumnName(i).data()); @@ -139,38 +128,41 @@ void VCellSundialsSolver::writeFileHeader(FILE* outputFile) { } } -void VCellSundialsSolver::printProgress(double currTime, double& lastPercentile, clock_t& lastTime, double increment, FILE* outputFile) { +void VCellSundialsSolver::printProgress(double currTime, double &lastPercentile, clock_t &lastTime, double increment, + FILE *outputFile) const { fflush(outputFile); if (currTime == STARTING_TIME) { // print 0% -#ifdef USE_MESSAGING + #ifdef USE_MESSAGING SimulationMessaging::getInstVar()->setWorkerEvent(new WorkerEvent(JOB_PROGRESS, lastPercentile, currTime)); -#else + #else printf("[[[progress:%lg%%]]]", lastPercentile*100.0); fflush(stdout); -#endif + #endif } else { clock_t currentTime = clock(); - double duration = (double)(currentTime - lastTime) / CLOCKS_PER_SEC; - if (duration >= 2){ //send out message every 2 seconds - double newPercentile = (long)((currTime - STARTING_TIME)/(ENDING_TIME - STARTING_TIME)/increment) * increment; + double duration = (double) (currentTime - lastTime) / CLOCKS_PER_SEC; + if (duration >= 2) { //send out message every 2 seconds + double newPercentile = (long) ((currTime - STARTING_TIME) / (ENDING_TIME - STARTING_TIME) / increment) * + increment; /*while (true) { double midTime = (percentile + increment) * (ENDING_TIME - STARTING_TIME); - if (STARTING_TIME + midTime > currTime) { + if (STARTING_TIME + midTime > currTime) { break; } percentile += increment; }*/ - if ( lastPercentile != newPercentile) { -#ifdef USE_MESSAGING - SimulationMessaging::getInstVar()->setWorkerEvent(new WorkerEvent(JOB_PROGRESS, newPercentile, currTime)); + if (lastPercentile != newPercentile) { + #ifdef USE_MESSAGING + SimulationMessaging::getInstVar()-> + setWorkerEvent(new WorkerEvent(JOB_PROGRESS, newPercentile, currTime)); SimulationMessaging::getInstVar()->setWorkerEvent(new WorkerEvent(JOB_DATA, newPercentile, currTime)); -#else + #else printf("[[[progress:%lg%%]]]", newPercentile*100.0); - printf("[[[data:%lg]]]", currTime); + printf("[[[data:%lg]]]", currTime); fflush(stdout); -#endif + #endif lastPercentile = newPercentile; lastTime = currentTime; } @@ -178,34 +170,22 @@ void VCellSundialsSolver::printProgress(double currTime, double& lastPercentile, } } -void VCellSundialsSolver::readInput(std::istream& inputstream) { +void VCellSundialsSolver::readInput(std::istream &inputstream) { try { - if (solver != 0) { - throw "readInput should only be called once"; - } - string name; + if (solver != 0) { throw "readInput should only be called once"; } + std::string name; while (!inputstream.eof()) { name = ""; inputstream >> name; - if (name.empty()) { - continue; - } + if (name.empty()) { continue; } if (name == "SOLVER") { inputstream >> name; - if (name != getSolverName()) { - throw "Wrong solver"; - } - } else if (name == "STARTING_TIME") { - inputstream >> STARTING_TIME; - } else if (name == "ENDING_TIME") { - inputstream >> ENDING_TIME; - } else if (name == "RELATIVE_TOLERANCE") { - inputstream >> RelativeTolerance; - } else if (name == "ABSOLUTE_TOLERANCE") { - inputstream >> AbsoluteTolerance; - } else if (name == "MAX_TIME_STEP") { - inputstream >> maxTimeStep; - } else if (name == "KEEP_EVERY") { + if (name != getSolverName()) { throw "Wrong solver"; } + } else if (name == "STARTING_TIME") { inputstream >> STARTING_TIME; } else if ( + name == "ENDING_TIME") { inputstream >> ENDING_TIME; } else if ( + name == "RELATIVE_TOLERANCE") { inputstream >> RelativeTolerance; } else if ( + name == "ABSOLUTE_TOLERANCE") { inputstream >> AbsoluteTolerance; } else if ( + name == "MAX_TIME_STEP") { inputstream >> maxTimeStep; } else if (name == "KEEP_EVERY") { inputstream >> keepEvery; } else if (name == "OUTPUT_TIME_STEP") { double outputTimeStep = 0.0; @@ -215,113 +195,90 @@ void VCellSundialsSolver::readInput(std::istream& inputstream) { while (STARTING_TIME + count * outputTimeStep < ENDING_TIME + 1E-12 * ENDING_TIME) { timePoint = STARTING_TIME + count * outputTimeStep; outputTimes.push_back(timePoint); - count ++; + count++; } ENDING_TIME = outputTimes[outputTimes.size() - 1]; } else if (name == "OUTPUT_TIMES") { - int totalNumTimePoints; + int totalNumTimePoints; double timePoint; inputstream >> totalNumTimePoints; - for (int i = 0; i < totalNumTimePoints; i ++) { + for (int i = 0; i < totalNumTimePoints; i++) { inputstream >> timePoint; - if (timePoint > STARTING_TIME && timePoint <= ENDING_TIME) { - outputTimes.push_back(timePoint); - } + if (timePoint > STARTING_TIME && timePoint <= ENDING_TIME) { outputTimes.push_back(timePoint); } } - if (outputTimes[outputTimes.size() - 1] < ENDING_TIME) { - outputTimes.push_back(ENDING_TIME); - } - } else if (name == "DISCONTINUITIES") { - readDiscontinuities(inputstream); - } else if (name == "NUM_PARAMETERS") { + if (outputTimes[outputTimes.size() - 1] < ENDING_TIME) { outputTimes.push_back(ENDING_TIME); } + } else if (name == "DISCONTINUITIES") { readDiscontinuities(inputstream); } else if ( + name == "NUM_PARAMETERS") { inputstream >> NPARAM; - paramNames = new string[NPARAM]; - for (int i = 0; i < NPARAM; i ++) { - inputstream >> paramNames[i]; - } + paramNames = new std::string[NPARAM]; + for (int i = 0; i < NPARAM; i++) { inputstream >> paramNames[i]; } } else if (name == "NUM_EQUATIONS") { inputstream >> NEQ; - variableNames = new string[NEQ]; - initialConditionExpressions = new VCell::Expression*[NEQ]; - readEquations(inputstream); - } else if (name == "EVENTS") { - readEvents(inputstream); - } else { - string msg = "Unexpected token \"" + name + "\" in the input file!"; + variableNames = new std::string[NEQ]; + initialConditionExpressions = new VCell::Expression *[NEQ]; + readEquations(inputstream); + } else if (name == "EVENTS") { readEvents(inputstream); } else { + std::string msg = "Unexpected token \"" + name + "\" in the input file!"; throw VCell::Exception(msg); } } initialize(); - } catch (char* ex) { - throw VCell::Exception(string("VCellSundialsSolver::readInput() : ") + ex); - } catch (VCell::Exception& ex) { - throw VCell::Exception(string("VCellSundialsSolver::readInput() : ") + ex.getMessage()); - } catch (...) { - throw "VCellSundialsSolver::readInput() : caught unknown exception"; + } catch (char *ex) { throw VCell::Exception(std::string("VCellSundialsSolver::readInput() : ") + ex); } catch ( + VCell::Exception &ex) { + throw VCell::Exception(std::string("VCellSundialsSolver::readInput() : ") + ex.getMessage()); } } -void VCellSundialsSolver::readEvents(std::istream& inputstream) { - string token; +void VCellSundialsSolver::readEvents(std::istream &inputstream) { + std::string token; inputstream >> numEvents; - events = new Event*[numEvents]; - for (int i = 0; i < numEvents; i ++) { + events = new Event *[numEvents]; + for (int i = 0; i < numEvents; i++) { events[i] = new Event(); - while (true) { + while (true) { // Break on "EVENTASSIGNMENTS" inputstream >> token; - if (token == "EVENT") { - inputstream >> events[i]->name; - } else if (token == "TRIGGER") { - try { - events[i]->triggerExpression = readExpression(inputstream); - } catch (VCell::Exception& ex) { - throw VCell::Exception(string("trigger expression") + " " + ex.getMessage()); + if (token == "EVENT") { inputstream >> events[i]->name; } else if (token == "TRIGGER") { + try { events[i]->triggerExpression = readExpression(inputstream); } catch (VCell::Exception &ex) { + throw VCell::Exception(std::string("trigger expression") + " " + ex.getMessage()); } } else if (token == "DELAY") { inputstream >> token; events[i]->bUseValuesAtTriggerTime = token == "true"; - try { - events[i]->delayDurationExpression = readExpression(inputstream); - } catch (VCell::Exception& ex) { - throw VCell::Exception(string("delay duration expression") + " " + ex.getMessage()); + try { events[i]->delayDurationExpression = readExpression(inputstream); } catch (VCell::Exception &ex) { + throw VCell::Exception(std::string("delay duration expression") + " " + ex.getMessage()); } } else if (token == "EVENTASSIGNMENTS") { - inputstream >> events[i]->numEventAssignments; - events[i]->eventAssignments = new EventAssignment*[events[i]->numEventAssignments]; - for (int j = 0; j < events[i]->numEventAssignments; j ++) { - EventAssignment* ea = new EventAssignment(); + int numEventAssignments; + inputstream >> numEventAssignments; + for (int j = 0; j < numEventAssignments; j++) { + EventAssignment *ea = new EventAssignment(); inputstream >> ea->varIndex; - try { - ea->assignmentExpression = readExpression(inputstream); - } catch (VCell::Exception& ex) { - throw VCell::Exception(string("event assignment expression") + " " + ex.getMessage()); + try { ea->assignmentExpression = readExpression(inputstream); } catch (VCell::Exception &ex) { + throw VCell::Exception(std::string("event assignment expression") + " " + ex.getMessage()); } - events[i]->eventAssignments[j] = ea; + events[i]->eventAssignmentsVec->push_back(ea); } break; - } else { - string msg = "Unexpected token \"" + token + "\" in the input file!"; - throw VCell::Exception(msg); - } + } else { throw VCell::Exception("Unexpected token \"" + token + "\" in the input file!"); } } } } -void VCellSundialsSolver::readDiscontinuities(std::istream& inputstream) { +void VCellSundialsSolver::readDiscontinuities(std::istream &inputstream) { inputstream >> numDiscontinuities; - odeDiscontinuities = new OdeDiscontinuity*[numDiscontinuities]; - string line; - string exp; - for (int i = 0; i < numDiscontinuities; i ++) { - OdeDiscontinuity* od = new OdeDiscontinuity; + odeDiscontinuities = new OdeDiscontinuity *[numDiscontinuities]; + std::string line; + std::string exp; + for (int i = 0; i < numDiscontinuities; i++) { + OdeDiscontinuity *od = new OdeDiscontinuity; inputstream >> od->discontinuitySymbol; line = ""; getline(inputstream, line); - string::size_type pos = line.find(";"); - if (pos == string::npos) { - string msg = string("discontinuity expression ") + BAD_EXPRESSION_MSG; + std::string::size_type pos = line.find(";"); + if (pos == std::string::npos) { + std::string msg = std::string("discontinuity expression ") + BAD_EXPRESSION_MSG; throw VCell::Exception(msg); } exp = line.substr(0, pos + 1); @@ -331,7 +288,7 @@ void VCellSundialsSolver::readDiscontinuities(std::istream& inputstream) { exp = line.substr(pos + 1); trimString(exp); if (*(exp.end() - 1) != ';') { - string msg = string("discontinuity root expression ") + BAD_EXPRESSION_MSG; + std::string msg = std::string("discontinuity root expression ") + BAD_EXPRESSION_MSG; throw VCell::Exception(msg); } od->rootFindingExpression = new VCell::Expression(exp); @@ -343,71 +300,57 @@ void VCellSundialsSolver::readDiscontinuities(std::istream& inputstream) { void VCellSundialsSolver::initialize() { // add parameters to symbol table numAllSymbols = 1 + NEQ + NPARAM + numDiscontinuities; - allSymbols = new string[numAllSymbols]; // t, variables, parameters, discontinuities + allSymbols = new std::string[numAllSymbols]; // t, variables, parameters, discontinuities //t allSymbols[0] = "t"; odeResultSet->addColumn("t"); // variables - for (int i = 0 ; i < NEQ; i ++) { + for (int i = 0; i < NEQ; i++) { allSymbols[1 + i] = variableNames[i]; odeResultSet->addColumn(variableNames[i]); } // parameters - for (int i = 0 ; i < NPARAM; i ++) { - allSymbols[1 + NEQ + i] = paramNames[i]; - } + for (int i = 0; i < NPARAM; i++) { allSymbols[1 + NEQ + i] = paramNames[i]; } //discontinuities - for (int i = 0 ; i < numDiscontinuities; i ++) { + for (int i = 0; i < numDiscontinuities; i++) { allSymbols[1 + NEQ + NPARAM + i] = odeDiscontinuities[i]->discontinuitySymbol; } // default symbol table has variables, parameters and discontinuities. - defaultSymbolTable = new SimpleSymbolTable(allSymbols, numAllSymbols); + defaultSymbolTable = new SimpleSymbolTable(allSymbols, numAllSymbols); // initial condition can only be function of parameters. initialConditionSymbolTable = new SimpleSymbolTable(allSymbols + 1 + NEQ, NPARAM); - for (int i = 0; i < NEQ; i ++) { - initialConditionExpressions[i]->bindExpression(initialConditionSymbolTable); - } + for (int i = 0; i < NEQ; i++) { initialConditionExpressions[i]->bindExpression(initialConditionSymbolTable); } if (numDiscontinuities > 0) { - rootsFound = new int[2*numDiscontinuities]; + rootsFound = new int[2 * numDiscontinuities]; discontinuityValues = new double[numDiscontinuities]; // discontinuities can't be function of discontinuity symbols discontinuitySymbolTable = new SimpleSymbolTable(allSymbols, 1 + NEQ + NPARAM); - for (int i = 0; i < numDiscontinuities; i ++) { + for (int i = 0; i < numDiscontinuities; i++) { odeDiscontinuities[i]->discontinuityExpression->bindExpression(discontinuitySymbolTable); odeDiscontinuities[i]->rootFindingExpression->bindExpression(discontinuitySymbolTable); } } - if (numEvents > 0) { - for (int i = 0; i < numEvents; i ++) { - events[i]->bind(defaultSymbolTable); - } - } - try { - values = new realtype[1 + NEQ + NPARAM + numDiscontinuities]; - tempRowData = new realtype[1 + NEQ]; - } catch (...) { - throw "Out of Memory"; - } + if (numEvents > 0) { for (int i = 0; i < numEvents; i++) { events[i]->bind(defaultSymbolTable); } } + values = new realtype[1 + NEQ + NPARAM + numDiscontinuities]; + tempRowData = new realtype[1 + NEQ]; y = N_VNew_Serial(NEQ); - if (y == 0) { - throw "Out of Memory"; - } + if (y == nullptr) { throw "Out of Memory"; } } bool VCellSundialsSolver::updateDiscontinuities(realtype t, bool bOnRootReturn) { - if (numDiscontinuities == 0) { - return false; - } + if (numDiscontinuities == 0) { return false; } bool bUpdated = false; updateTandVariableValues(t, y); - for (int i = 0; i < numDiscontinuities; i ++) { - std::cout << odeDiscontinuities[i]->discontinuitySymbol << " " << odeDiscontinuities[i]->discontinuityExpression->infix() << " " << discontinuityValues[i]; + for (int i = 0; i < numDiscontinuities; i++) { + std::cout << odeDiscontinuities[i]->discontinuitySymbol << " " << odeDiscontinuities[i]->discontinuityExpression + ->infix() << " " << + discontinuityValues[i]; if (bOnRootReturn) { if (rootsFound[2 * i] && rootsFound[2 * i + 1]) { std::cout << " inverted "; @@ -417,66 +360,58 @@ bool VCellSundialsSolver::updateDiscontinuities(realtype t, bool bOnRootReturn) std::cout << " evaluated "; double oldValue = discontinuityValues[i]; discontinuityValues[i] = odeDiscontinuities[i]->discontinuityExpression->evaluateVector(values); - if (oldValue != discontinuityValues[i]) { - bUpdated = true; - } - } else { - std::cout << " nonroot "; - } + if (oldValue != discontinuityValues[i]) { bUpdated = true; } + } else { std::cout << " nonroot "; } } else { std::cout << " evaluated after event execution "; double oldValue = discontinuityValues[i]; discontinuityValues[i] = odeDiscontinuities[i]->discontinuityExpression->evaluateVector(values); - if (oldValue != discontinuityValues[i]) { - bUpdated = true; - } + if (oldValue != discontinuityValues[i]) { bUpdated = true; } } std::cout << discontinuityValues[i] << std::endl; } std::cout << std::endl; // copy discontinuity values to values to evaluate RHS - if (bUpdated) { - memcpy(values + 1 + NEQ + NPARAM, discontinuityValues, numDiscontinuities * sizeof(double)); - } + if (bUpdated) { memcpy(values + 1 + NEQ + NPARAM, discontinuityValues, numDiscontinuities * sizeof(double)); } return bUpdated; } void VCellSundialsSolver::initDiscontinuities() { - if (numDiscontinuities == 0) { - return; - } + if (numDiscontinuities == 0) { return; } updateTandVariableValues(STARTING_TIME, y); - // init discontinuities - for (int i = 0; i < numDiscontinuities; i ++) { + // init discontinuities + for (int i = 0; i < numDiscontinuities; i++) { discontinuityValues[i] = odeDiscontinuities[i]->discontinuityExpression->evaluateVector(values); } // copy discontinuity values to values to evaluate RHS memcpy(values + 1 + NEQ + NPARAM, discontinuityValues, numDiscontinuities * sizeof(double)); } -void VCellSundialsSolver::checkDiscontinuityConsistency() { - if (numDiscontinuities == 0) { - return; - } +void VCellSundialsSolver::checkDiscontinuityConsistency() const { + if (numDiscontinuities == 0) { return; } - for (int i = 0; i < numDiscontinuities; i ++) { + for (int i = 0; i < numDiscontinuities; i++) { double realValue = odeDiscontinuities[i]->discontinuityExpression->evaluateVector(values); if (discontinuityValues[i] != realValue) { - stringstream ss; - ss << "at time " << values[0] << ", discontinuity " << odeDiscontinuities[i]->discontinuityExpression->infix() << " evaluated to " << (realValue ? "TRUE" : "FALSE") << ", solver assumed " << (discontinuityValues[i] ? "TRUE" : "FALSE") << std::endl; + std::stringstream ss; + ss << "at time " << values[0] << ", discontinuity " << odeDiscontinuities[i]->discontinuityExpression-> + infix() << " evaluated to " << + (realValue ? "TRUE" : "FALSE") << ", solver assumed " << (discontinuityValues[i] ? "TRUE" : "FALSE") + << std::endl; throw ss.str(); } } } void VCellSundialsSolver::solveInitialDiscontinuities(double t) { - std::cout << "------------------solveInitialDiscontinuities--at-time-" << t << "--------------------------" << std::endl; + std::cout << "------------------solveInitialDiscontinuities--at-time-" << t << "--------------------------" << + std::endl; bool bFoundRoot = false; - string roots_at_initial_str = ""; + std::string roots_at_initial_str; updateTandVariableValues(t, y); - for (int i = 0; i < numDiscontinuities; i ++) { + for (int i = 0; i < numDiscontinuities; i++) { double v = odeDiscontinuities[i]->rootFindingExpression->evaluateVector(values); if (fabs(v) < AbsoluteTolerance) { roots_at_initial_str += odeDiscontinuities[i]->discontinuityExpression->infix() + "; "; @@ -485,23 +420,23 @@ void VCellSundialsSolver::solveInitialDiscontinuities(double t) { } if (bFoundRoot) { - std::cout << "solveInitialDiscontinuities() : roots found at time " << t << "; " << roots_at_initial_str << std::endl; + std::cout << "solveInitialDiscontinuities() : roots found at time " << t << "; " << roots_at_initial_str << + std::endl; int count = 0; - int maxCount = (int)pow(2.0, numDiscontinuities); + int maxCount = (int) pow(2.0, numDiscontinuities); while (count < maxCount) { - try { - if (!fixInitialDiscontinuities(t)) { - break; - } - } catch (const char* err) { - stringstream str; - str << "found discontinuities at time " << t << " but unable to initialize : " << err << "\nDiscontinuities at time 0 are : " << roots_at_initial_str; + try { if (!fixInitialDiscontinuities(t)) { break; } } catch (const char *err) { + std::stringstream str; + str << "found discontinuities at time " << t << " but unable to initialize : " << err << + "\nDiscontinuities at time 0 are : " << roots_at_initial_str; throw str.str(); } - count ++; + count++; } if (count >= maxCount) { - string str = "found discontinuities at time 0 but unable to initialize due to max iterations.\nDiscontinuities at time 0 are : " + roots_at_initial_str; + std::string str = + "found discontinuities at time 0 but unable to initialize due to max iterations.\nDiscontinuities at time 0 are : " + + roots_at_initial_str; throw str; } } @@ -512,16 +447,16 @@ void VCellSundialsSolver::printVariableValues(realtype t) { updateTandVariableValues(t, y); std::cout << "variable values are" << std::endl; std::cout << "t " << values[0] << std::endl; - for (int i = 0; i < NEQ; i ++) { - std::cout << variableNames[i] << " " << values[i + 1] << std::endl; - } + for (int i = 0; i < NEQ; i++) { std::cout << variableNames[i] << " " << values[i + 1] << std::endl; } std::cout << std::endl; } void VCellSundialsSolver::printDiscontinuityValues() { std::cout << std::endl << "discontinuities values are" << std::endl; - for (int i = 0; i < numDiscontinuities; i ++) { - std::cout << odeDiscontinuities[i]->discontinuitySymbol << " " << odeDiscontinuities[i]->discontinuityExpression->infix() << " " << discontinuityValues[i] << std::endl; + for (int i = 0; i < numDiscontinuities; i++) { + std::cout << odeDiscontinuities[i]->discontinuitySymbol << " " << odeDiscontinuities[i]->discontinuityExpression + ->infix() << " " << + discontinuityValues[i] << std::endl; } std::cout << std::endl; } @@ -531,7 +466,7 @@ int VCellSundialsSolver::RootFn(realtype t, N_Vector y, realtype *gout) { //cout << "RootFn " << endl; //printVariableValues(); - for (int i = 0; i < numDiscontinuities; i ++) { + for (int i = 0; i < numDiscontinuities; i++) { double r = odeDiscontinuities[i]->rootFindingExpression->evaluateVector(values); if (r == 0) { gout[2 * i] = 1e-200; @@ -539,95 +474,85 @@ int VCellSundialsSolver::RootFn(realtype t, N_Vector y, realtype *gout) { } else { gout[2 * i] = r; gout[2 * i + 1] = r; - } + } //cout << "gout[" << i << "]=" << gout[i] << endl; - } + } return 0; } void VCellSundialsSolver::testEventTriggers(realtype Time) { - if (numEvents == 0) { - return; - } + if (numEvents == 0) { return; } updateTandVariableValues(Time, y); - for (int i = 0; i < numEvents; i ++) { + + std::vector eventExecutions; + for (int i = 0; i < numEvents; i++) { bool oldTriggerValue = events[i]->triggerValue; bool newTriggerValue = events[i]->triggerExpression->evaluateVector(values) != 0.0; events[i]->triggerValue = newTriggerValue; if (!oldTriggerValue && newTriggerValue) { // triggered - EventExecution* ee = new EventExecution(events[i]); - ee->exeTime = Time; + eventExecutions.emplace_back(new EventExecution(events[i])); + eventExecutions.back()->exeTime = Time; if (events[i]->hasDelay()) { - ee->exeTime = Time + events[i]->delayDurationExpression->evaluateVector(values); + eventExecutions.back()->exeTime = Time + events[i]->delayDurationExpression->evaluateVector(values); } if (events[i]->bUseValuesAtTriggerTime) { - ee->targetValues = new double[events[i]->numEventAssignments]; - for (int j = 0; j < events[i]->numEventAssignments; j ++) { - ee->targetValues[j] = events[i]->eventAssignments[j]->assignmentExpression->evaluateVector(values); - } - } - bool bInserted = false; - for (std::list::iterator iter = eventExeList.begin(); iter != eventExeList.end(); iter ++) { - if ((*iter)->exeTime > ee->exeTime) { - eventExeList.insert(iter, ee); // sort them by execution time - bInserted = true; - break; + const unsigned long numEventAssignments = events[i]->eventAssignmentsVec->size(); + eventExecutions.back()->targetValues = new double[numEventAssignments]; + for (unsigned long j = 0; j < numEventAssignments; j++) { + eventExecutions.back()->targetValues[j] = events[i]->eventAssignmentsVec->at(j)->assignmentExpression->evaluateVector(values); } } - if (!bInserted) { - eventExeList.push_back(ee); - } } } + auto compLambda = [](const EventExecution *e1, const EventExecution *e2) { + return e1->exeTime < e2->exeTime; + }; + std::ranges::sort(eventExecutions, compLambda); + std::list eventExecutionsSorted(std::make_move_iterator(eventExecutions.begin()), std::make_move_iterator(eventExecutions.end())); + eventExeList.splice(eventExeList.end(), eventExecutionsSorted); } -bool VCellSundialsSolver::executeEvents(realtype Time) { - if (numEvents == 0) { - return false; - } - testEventTriggers(Time); +bool VCellSundialsSolver::executeEvents(const realtype realTimeVar) { + if (numEvents == 0) { return false; } + testEventTriggers(realTimeVar); static double epsilon = 1e-15; bool bExecuted = false; - while (eventExeList.size() > 0) { - std::list::iterator iter = eventExeList.begin(); - EventExecution* ee = *iter; - - if (ee->exeTime > Time + epsilon) { // not time yet - return bExecuted; + while (!eventExeList.empty()) { + auto iter = eventExeList.begin(); + EventExecution *ee = *iter; + + if (ee->exeTime > realTimeVar + epsilon) return bExecuted; // not time yet + if (ee->exeTime < realTimeVar) { + std::stringstream ss; + ss << "missed Event '" << ee->event0->name << "' with trigger " << ee->event0->triggerExpression->infix() + << ", scheduled time = " << ee->exeTime << ", current time = " << realTimeVar << std::endl; + throw ss.str(); } - if (fabs(ee->exeTime - Time) < epsilon) { // execute - updateTandVariableValues(Time, y); - double* y_data = NV_DATA_S(y); // assign the values - for (int i = 0; i < ee->event0->numEventAssignments; i ++) { - EventAssignment* ea = ee->event0->eventAssignments[i]; - if (ee->event0->bUseValuesAtTriggerTime) { - y_data[ea->varIndex] = ee->targetValues[i]; - } else { + if (fabs(ee->exeTime - realTimeVar) < epsilon) { // execute + updateTandVariableValues(realTimeVar, y); + double *y_data = NV_DATA_S(y); // assign the values + for (int i = 0; i < ee->event0->eventAssignmentsVec->size(); i++) { + EventAssignment *ea = ee->event0->eventAssignmentsVec->at(i); + if (ee->event0->bUseValuesAtTriggerTime) { y_data[ea->varIndex] = ee->targetValues[i]; } else { y_data[ea->varIndex] = ea->assignmentExpression->evaluateVector(values); } } - std::cout << std::endl << "Executed event " << ee->event0->name << " at time " << Time << std::endl; + std::cout << std::endl << "Executed event " << ee->event0->name << " at time " << realTimeVar << std::endl; eventExeList.pop_front(); // delete from the list delete ee; bExecuted = true; - testEventTriggers(Time); // retest all triggers again. - } else if (ee->exeTime < Time) { - stringstream ss; - ss << "missed Event '" << ee->event0->name << "' with trigger " << ee->event0->triggerExpression->infix() - << ", scheduled time = " << ee->exeTime << ", current time = " << Time << std::endl; - throw ss.str(); + testEventTriggers(realTimeVar); // retest all triggers again. } - } return bExecuted; } double VCellSundialsSolver::getNextEventTime() { - if (eventExeList.size() > 0) { - std::list::iterator iter = eventExeList.begin(); - EventExecution* ee = *iter; + if (!eventExeList.empty()) { + auto iter = eventExeList.begin(); + EventExecution *ee = *iter; return ee->exeTime; } @@ -635,9 +560,7 @@ double VCellSundialsSolver::getNextEventTime() { } void VCellSundialsSolver::checkStopRequested(double time, long numIterations) { -#ifdef USE_MESSAGING - if (SimulationMessaging::getInstVar()->isStopRequested()) { - throw StoppedByUserException("stopped by user"); - } -#endif + #ifdef USE_MESSAGING + if (SimulationMessaging::getInstVar()->isStopRequested()) { throw StoppedByUserException("stopped by user"); } + #endif } diff --git a/IDAWin/VCellSundialsSolver.h b/IDAWin/VCellSundialsSolver.h index a0dab9b1c..b3c26f638 100644 --- a/IDAWin/VCellSundialsSolver.h +++ b/IDAWin/VCellSundialsSolver.h @@ -5,8 +5,6 @@ #include #include #include -#include -#include #include @@ -15,8 +13,6 @@ #include #include -#include - class SymbolTable; class OdeResultSet; @@ -32,47 +28,45 @@ struct EventAssignment { ~EventAssignment() { delete assignmentExpression; } - void bind(SymbolTable* symbolTable) { + void bind(SymbolTable* symbolTable) const { assignmentExpression->bindExpression(symbolTable); } }; struct Event { - string name; + std::string name; VCell::Expression* triggerExpression; bool bUseValuesAtTriggerTime; VCell::Expression* delayDurationExpression; - int numEventAssignments; - EventAssignment** eventAssignments; + std::vector* eventAssignmentsVec; bool triggerValue; Event() { - bUseValuesAtTriggerTime = false; - triggerExpression = 0; - triggerValue = false; - delayDurationExpression = 0; - eventAssignments = 0; + this->bUseValuesAtTriggerTime = false; + this->triggerExpression = nullptr; + this->triggerValue = false; + this->delayDurationExpression = nullptr; + this->eventAssignmentsVec = new std::vector; } ~Event() { - delete triggerExpression; - delete delayDurationExpression; - for (int i = 0; i < numEventAssignments; i ++) { - delete eventAssignments[i]; - } - delete[] eventAssignments; + delete this->triggerExpression; + delete this->delayDurationExpression; + for (const EventAssignment* elem : *this->eventAssignmentsVec) delete elem; + this->eventAssignmentsVec->clear(); + delete this->eventAssignmentsVec; } - bool hasDelay() { - return delayDurationExpression != 0; + [[nodiscard]] bool hasDelay() const { + return this->delayDurationExpression != nullptr; } - void bind(SymbolTable* symbolTable) { - triggerExpression->bindExpression(symbolTable); - if (delayDurationExpression != 0) { - delayDurationExpression->bindExpression(symbolTable); + void bind(SymbolTable* symbolTable) const { + this->triggerExpression->bindExpression(symbolTable); + if (this->delayDurationExpression != nullptr) { + this->delayDurationExpression->bindExpression(symbolTable); } - for (int i = 0; i < numEventAssignments; i ++) { - eventAssignments[i]->bind(symbolTable); + for (const EventAssignment* elem : *this->eventAssignmentsVec) { + elem->bind(symbolTable); } } }; @@ -83,22 +77,23 @@ struct EventExecution { double* targetValues; EventExecution(Event* e) { - event0 = e; - targetValues = 0; + this->exeTime = RCONST(0.0); + this->event0 = e; + this->targetValues = nullptr; } ~EventExecution() { - delete targetValues; + delete this->targetValues; } }; struct OdeDiscontinuity { - string discontinuitySymbol; + std::string discontinuitySymbol; VCell::Expression* discontinuityExpression; VCell::Expression* rootFindingExpression; ~OdeDiscontinuity() { - delete discontinuityExpression; - delete rootFindingExpression; + delete this->discontinuityExpression; + delete this->rootFindingExpression; } }; @@ -108,14 +103,14 @@ class VCellSundialsSolver { virtual ~VCellSundialsSolver(); void readInput(std::istream& inputstream); - virtual void solve(double* paramValues=0, bool bPrintProgress=false, FILE* outputFile=0, void (*checkStopRequested)(double, long)=0) = 0; - OdeResultSet* getResultSet() { return odeResultSet; } - int getNumEquations() { return NEQ; } - VCell::Expression** getInitialConditionExpressions() { return initialConditionExpressions; } - void setStartingTime(realtype newStartingTime) { STARTING_TIME = newStartingTime; } - void setEndingTime(realtype newEndingTime) { ENDING_TIME = newEndingTime; } - void setOutputTimes(int count, double* newOutputTimes); - SymbolTable* getSymbolTable() { return defaultSymbolTable;} + virtual void solve(double* paramValues=nullptr, bool bPrintProgress=false, FILE* outputFile=nullptr, void (*checkStopRequested)(double, long)=nullptr) = 0; + OdeResultSet* getResultSet() { return this->odeResultSet; } + [[nodiscard]] int getNumEquations() const { return this->NEQ; } + VCell::Expression** getInitialConditionExpressions() { return this->initialConditionExpressions; } + void setStartingTime(realtype newStartingTime) { this->STARTING_TIME = newStartingTime; } + void setEndingTime(realtype newEndingTime) { this->ENDING_TIME = newEndingTime; } + //void setOutputTimes(int count, double* newOutputTimes); + SymbolTable* getSymbolTable() { return this->defaultSymbolTable;} static void checkStopRequested(double, long); @@ -131,7 +126,7 @@ class VCellSundialsSolver { OdeResultSet* odeResultSet; // mainly for parameter optimization use but it also stores column names void* solver; // the memory for solver - string recoverableErrMsg; + std::string recoverableErrMsg; int NEQ; int NPARAM; @@ -141,7 +136,7 @@ class VCellSundialsSolver { realtype AbsoluteTolerance; long keepEvery; double maxTimeStep; - vector outputTimes; + std::vector outputTimes; double* tempRowData; // data for current time to be written to output file and to be added to odeResultSet int numDiscontinuities; @@ -150,9 +145,9 @@ class VCellSundialsSolver { double* discontinuityValues; int* rootsFound; - string* paramNames; - string* variableNames; // variables - string* allSymbols; + std::string* paramNames; + std::string* variableNames; // variables + std::string* allSymbols; int numAllSymbols; SymbolTable* defaultSymbolTable; @@ -161,7 +156,7 @@ class VCellSundialsSolver { virtual void updateTempRowData(double currTime); void writeFileData(FILE* outputFile); void writeFileHeader(FILE* outputFile); - void printProgress(double currTime, double& lastPercentile, clock_t& lastTime, double increment, FILE* outputFile); + void printProgress(double currTime, double& lastPercentile, clock_t& lastTime, double increment, FILE* outputFile) const; void readDiscontinuities(std::istream& inputstream); virtual void readEquations(std::istream& inputstream) = 0; @@ -169,7 +164,7 @@ class VCellSundialsSolver { void initDiscontinuities(); bool updateDiscontinuities(realtype t, bool bOnRootReturn); - void checkDiscontinuityConsistency(); + void checkDiscontinuityConsistency() const; void solveInitialDiscontinuities(double t); virtual bool fixInitialDiscontinuities(double t)=0; @@ -179,10 +174,10 @@ class VCellSundialsSolver { virtual void updateTandVariableValues(realtype t, N_Vector y)=0; int RootFn(realtype t, N_Vector y, realtype *gout); - virtual string getSolverName()=0; + virtual std::string getSolverName()=0; - VCell::Expression* readExpression(std::istream& inputstream); - bool executeEvents(realtype Time); + static VCell::Expression* readExpression(std::istream& inputstream); + bool executeEvents(realtype realTimeVar); double getNextEventTime(); private: diff --git a/VCellMessaging/src/SimulationMessaging.cpp b/VCellMessaging/src/SimulationMessaging.cpp index cb2ee8c0c..e127d2b85 100644 --- a/VCellMessaging/src/SimulationMessaging.cpp +++ b/VCellMessaging/src/SimulationMessaging.cpp @@ -3,18 +3,12 @@ #include #include #include -using std::cerr; -using std::cout; -using std::endl; #include #ifdef USE_MESSAGING #if ( !defined(_WIN32) && !defined(_WIN64) ) // UNIX #include -#include -#include -#include -#include +#include #endif static const char* TIMETOLIVE_PROPERTY = "JMSTimeToLive"; @@ -283,19 +277,19 @@ void SimulationMessaging::sendStatus() { const char* messaging_http_url = s_url.c_str(); curl_easy_setopt(curl, CURLOPT_URL, messaging_http_url); - cout << "curl -XPOST " << messaging_http_url << endl; + std::cout << "curl -XPOST " << messaging_http_url << std::endl; // // print message to stdout // - cout << "!!!SimulationMessaging::sendStatus [" << (long)m_simKey << ":" << getStatusString(workerEvent->status); + std::cout << "!!!SimulationMessaging::sendStatus [" << (long)m_simKey << ":" << getStatusString(workerEvent->status); if (revisedMsg != NULL) { - cout << ":" << revisedMsg; + std::cout << ":" << revisedMsg; } else { - cout << ":" << workerEvent->progress << ":" << workerEvent->timepoint; + std::cout << ":" << workerEvent->progress << ":" << workerEvent->timepoint; } - cout << "]" << endl; + std::cout << "]" << std::endl; @@ -324,7 +318,7 @@ void SimulationMessaging::sendStatus() { #if ( defined(_WIN32) || defined(_WIN64) ) SetEvent(hMessagingThreadEndEvent); #else // UNIX - cout << "!!!thread exiting" << endl; + std::cout << "!!!thread exiting" << std::endl; pthread_exit(NULL); #endif } @@ -422,7 +416,7 @@ bool SimulationMessaging::lockMessaging() return true; #else // UNIX if (pthread_mutex_lock(&mutex_messaging)) { - cout << "Cannot acquire mutex, fatal error." << endl; + std::cout << "Cannot acquire mutex, fatal error." << std::endl; exit(1); } #endif @@ -591,7 +585,7 @@ void SimulationMessaging::waitUntilFinished() { break; } #else - cout << "!!!waiting for thread to exit" << endl; + std::cout << "!!!waiting for thread to exit" << std::endl; pthread_join(newWorkerEventThread, NULL); #endif } @@ -746,7 +740,7 @@ void* startMessagingThread(void* lpParam){ break; default: - std::cout<< "Wait error: " << waitReturn << endl; + std::cout<< "Wait error: " << waitReturn << std::endl; break; } } From b1be7be7f83ed8c5d97da368533ced509ba5c236 Mon Sep 17 00:00:00 2001 From: Logan Drescher Date: Wed, 25 Jun 2025 14:46:52 -0400 Subject: [PATCH 03/34] Converted to Argparse entry --- IDAWin/SundialsSolverStandalone.cpp | 376 +++++++++++----------------- 1 file changed, 147 insertions(+), 229 deletions(-) diff --git a/IDAWin/SundialsSolverStandalone.cpp b/IDAWin/SundialsSolverStandalone.cpp index e3e2ec661..0c7a95747 100644 --- a/IDAWin/SundialsSolverStandalone.cpp +++ b/IDAWin/SundialsSolverStandalone.cpp @@ -1,273 +1,191 @@ -// Debug -#ifdef _DEBUG - //#define _CRTDBG_MAP_ALLOC - #ifdef _CRTDBG_MAP_ALLOC - #include - #include - #else - //#include - #endif -#endif // Messaging #ifdef USE_MESSAGING #include +#include #endif // Standard Includes #include #include #include -#include -#include // Local Includes #include "VCellCVodeSolver.h" #include "VCellIDASolver.h" -#include "OdeResultSet.h" #include "StoppedByUserException.h" #include - - - +#include #define CVODE_SOLVER "CVODE" #define IDA_SOLVER "IDA" -void printUsage() { - std::string usageMessage{"Usage: SundialsSolverStandalone input output"}; - #ifdef USE_MESSAGING - usageMessage += " [-tid 0]"; - #endif - std::cout << usageMessage << std::endl; -} - -void loadJMSInfo(std::istream& ifsInput, int taskID) { - char *broker = new char[256]; - char *smqusername = new char[256]; - char *password = new char[256]; - char *qname = new char[256]; - char *tname = new char[256]; - char *vcusername = new char[256]; - string nextToken; - int simKey, jobIndex; - - while (!ifsInput.eof()) { - nextToken = ""; - ifsInput >> nextToken; - if (nextToken.size() == 0) { - continue; - } else if (nextToken[0] == '#') { - getline(ifsInput, nextToken); - continue; - } else if (nextToken == "JMS_PARAM_END") { - break; - } else if (nextToken == "JMS_BROKER") { - std::string brokerStr; - ifsInput >> brokerStr; - memset(broker, 0, 256 * sizeof(char)); - strncpy(broker, brokerStr.c_str(), 256); - } else if (nextToken == "JMS_USER") { - std::string usernameStr, passwordStr; - ifsInput >> usernameStr >> passwordStr; - memset(smqusername, 0, 256 * sizeof(char)); - memset(password, 0, 256 * sizeof(char)); - strncpy(smqusername, usernameStr.c_str(), 256); - strncpy(password, passwordStr.c_str(), 256); - } else if (nextToken == "JMS_QUEUE") { - std::string qnameStr; - ifsInput >> qnameStr; - memset(qname, 0, 256 * sizeof(char)); - strncpy(qname, qnameStr.c_str(), 256); - } else if (nextToken == "JMS_TOPIC") { - std::string topicStr; - ifsInput >> topicStr; - memset(tname, 0, 256 * sizeof(char)); - strncpy(tname, topicStr.c_str(), 256); - } else if (nextToken == "VCELL_USER") { - std::string vcusernameStr; - ifsInput >> vcusernameStr; - memset(vcusername, 0, 256 * sizeof(char)); - strncpy(vcusername, vcusernameStr.c_str(), 256); - } else if (nextToken == "SIMULATION_KEY") { - ifsInput >> simKey; - continue; - } else if (nextToken == "JOB_INDEX") { - ifsInput >> jobIndex; - continue; - } - } - -#ifdef USE_MESSAGING - if (taskID >= 0) { - SimulationMessaging::create(broker, smqusername, password, qname, tname, vcusername, simKey, jobIndex, taskID); - } else { - SimulationMessaging::create(); - } -#endif -} - -void errExit(int returnCode, std::string& errorMsg) { -#ifdef USE_MESSAGING - if (returnCode != 0) { - if (SimulationMessaging::getInstVar() != 0 && !SimulationMessaging::getInstVar()->isStopRequested()) { - SimulationMessaging::getInstVar()->setWorkerEvent(new WorkerEvent(JOB_FAILURE, errorMsg.c_str())); - } - } - if (SimulationMessaging::getInstVar() != 0) { - SimulationMessaging::getInstVar()->waitUntilFinished(); - delete SimulationMessaging::getInstVar(); - } else { - if (returnCode != 0) { - std::cerr << errorMsg << std::endl; - } - } -#else - if (returnCode != 0) { - std::cerr << errorMsg << std::endl; - } -#endif -} +int parseAndRunWithArgParse(int argc, char *argv[]); +void activateSolver(std::ifstream& inputFileStream, FILE* outputFile, int taskID); +void loadJMSInfo(std::istream &ifsInput, int taskID); +void errExit(int returnCode, std::string &errorMsg); int main(int argc, char *argv[]) { - std::cout - << "Sundials Standalone version " << g_GIT_DESCRIBE - << std::endl; std::cout << std::setprecision(20); + return parseAndRunWithArgParse(argc, argv); +} +int parseAndRunWithArgParse(int argc, char *argv[]) { int taskID = -1; - string inputfname; - string outputfname; - string solver; - string errMsg; + std::string inputFilePath; + std::string outputFilePath; + std::string errMsg; int returnCode = 0; - if (argc < 3) { - std::cout << "Missing arguments!" << std::endl; - printUsage(); - exit(1); + argparse::ArgumentParser argumentParser("program_name", g_GIT_DESCRIBE); + argumentParser.add_argument("input").help("path to directory with input files.").store_into(inputFilePath); + argumentParser.add_argument("output").help("path to directory for output files.").store_into(outputFilePath); + #ifdef USE_MESSAGING + argumentParser.add_argument("-tid").help("path to solver to run.").store_into(taskID); + #endif + + try { + argumentParser.parse_args(argc, argv); } - for (int i = 1; i < argc; i ++) { - if (!strcmp(argv[i], "-tid")) { -#ifdef USE_MESSAGING - i ++; - if (i >= argc) { - std::cout << "Missing taskID!" << std::endl; - printUsage(); - exit(1); - } - for (int j = 0; j < (int)strlen(argv[i]); j ++) { - if (argv[i][j] < '0' || argv[i][j] > '9') { - std::cout << "Wrong argument : " << argv[i] << ", taskID must be an integer!" << std::endl; - printUsage(); - exit(1); - } - } - taskID = atoi(argv[i]); -#else - std::cout << "Wrong argument : " << argv[i] << std::endl; - printUsage(); - exit(1); -#endif - } else { - inputfname = argv[i]; - i ++; - outputfname = argv[i]; - } + catch (const std::exception& err) { + std::cerr << err.what() << std::endl; + std::cerr << argumentParser; + return -1; } - FILE* outputFile = NULL; - std::ifstream inputstream(inputfname.c_str()); - try { - if (!inputstream.is_open()) { - throw std::string("input file [") + inputfname + "] doesn't exit!"; - } + FILE *outputFile = NULL; + std::ifstream inputFileStream{inputFilePath}; + try { + if (!inputFileStream.is_open()) { throw std::runtime_error("input file [" + inputFilePath + "] doesn't exit!"); } - // Open the output file... + // Open the output file... if ((outputFile = fopen(argv[2], "w")) == NULL) { - throw std::string("Could not open output file[") + outputfname + "] for writing."; - } - - string nextToken; - - while (!inputstream.eof()) { - nextToken = ""; - inputstream >> nextToken; - if (nextToken.empty()) { - continue; - } else if (nextToken[0] == '#') { - getline(inputstream, nextToken); - continue; - } else if (nextToken == "JMS_PARAM_BEGIN") { - loadJMSInfo(inputstream, taskID); -#ifdef USE_MESSAGING - SimulationMessaging::getInstVar()->start(); // start the thread -#endif - } else if (nextToken == "SOLVER") { - inputstream >> solver; - break; - } - } -#ifdef USE_MESSAGING - // should only happen during testing for solver compiled with messaging but run locally. - if (SimulationMessaging::getInstVar() == nullptr) { - SimulationMessaging::create(); + throw std::runtime_error("Could not open output file[" + outputFilePath + "] for writing."); } -#endif - - if (solver.empty()) { - throw "Solver not defined "; - } - -#ifdef _CRTDBG_MAP_ALLOC - _CrtMemState s1, s2, s3; -#endif - errMsg += solver + " solver failed : "; -#ifdef _CRTDBG_MAP_ALLOC - _CrtMemCheckpoint( &s1 ); -#endif - VCellSundialsSolver* vss = 0; - if (solver == IDA_SOLVER) { - vss = new VCellIDASolver(); - } else if (solver == CVODE_SOLVER) { - vss = new VCellCVodeSolver(); - } else { - std::stringstream ss; - ss << "Solver " << solver << " not defined!"; - throw ss.str(); - } - vss->readInput(inputstream); - vss->solve(0, true, outputFile, VCellSundialsSolver::checkStopRequested); + activateSolver(inputFileStream, outputFile, taskID); - delete vss; -#ifdef _CRTDBG_MAP_ALLOC - _CrtMemCheckpoint( &s2 ); - if ( _CrtMemDifference( &s3, &s1, &s2) ) - _CrtMemDumpStatistics( &s3 ); - _CrtDumpMemoryLeaks(); -#endif - } catch (const char* ex) { + } catch (const char *ex) { errMsg += ex; returnCode = -1; - } catch (string& ex) { + } catch (std::string &ex) { errMsg += ex; returnCode = -1; - } catch (StoppedByUserException) { - returnCode = 0; // stopped by user; - } catch (VCell::Exception& ex) { + } catch (StoppedByUserException&) { + returnCode = 0; // stopped by user; + } catch (VCell::Exception &ex) { errMsg += ex.getMessage(); returnCode = -1; + } catch (const std::exception& err) { + errMsg += err.what(); + returnCode = -1; } catch (...) { errMsg += "unknown error"; returnCode = -1; } - - if (outputFile != NULL) { - fclose(outputFile); - } - if (inputstream.is_open()) { - inputstream.close(); - } + if (outputFile != NULL) { fclose(outputFile); } + if (inputFileStream.is_open()) { inputFileStream.close(); } errExit(returnCode, errMsg); -#ifdef _CRTDBG_MAP_ALLOC - _CrtDumpMemoryLeaks(); -#endif return returnCode; } + +void activateSolver(std::ifstream& inputFileStream, FILE* outputFile, int taskID) { + std::string solver; + + while (!inputFileStream.eof()) { // Note break statement if "SOLVER" encountered + std::string nextToken; + inputFileStream >> nextToken; + if (nextToken.empty()) continue; + if (nextToken[0] == '#') getline(inputFileStream, nextToken); + else if (nextToken == "JMS_PARAM_BEGIN") { + loadJMSInfo(inputFileStream, taskID); + #ifdef USE_MESSAGING + SimulationMessaging::getInstVar()->start(); // start the thread + #endif + } else if (nextToken == "SOLVER") { + inputFileStream >> solver; + break; + } + } + #ifdef USE_MESSAGING + // should only happen during testing for solver compiled with messaging but run locally. + if (SimulationMessaging::getInstVar() == nullptr) { SimulationMessaging::create(); } + #endif + + if (solver.empty()) { throw "Solver not defined "; } + VCellSundialsSolver *vss = nullptr; + if (solver == IDA_SOLVER) { + vss = new VCellIDASolver(); + } else if (solver == CVODE_SOLVER) { + vss = new VCellCVodeSolver(); + } else { + std::stringstream ss; + ss << "Solver " << solver << " not defined!"; + throw ss.str(); + } + vss->readInput(inputFileStream); + vss->solve(nullptr, true, outputFile, VCellSundialsSolver::checkStopRequested); + + delete vss; +} + +void loadJMSInfo(std::istream &ifsInput, int taskID) { + #ifndef USE_MESSAGING + return; // Only useful for messaging; let's not waste time! + #else + + if (taskID < 0) { + SimulationMessaging::create(); + return; // No need to do any parsing + } + std::string broker; + std::string smqUserName; + std::string password; + std::string qName; + std::string topicName; + std::string vCellUsername; + int simKey, jobIndex; + + while (!ifsInput.eof()) { + std::string nextToken; + ifsInput >> nextToken; + if (nextToken.empty()) continue; + if (nextToken[0] == '#') { + // getline(ifsInput, nextToken); // Is this ignoring because of a comment? + ifsInput.ignore('\n'); + continue; + } + if (nextToken == "JMS_PARAM_END") { ifsInput.ignore(EOF); } else if ( + nextToken == "JMS_BROKER") { ifsInput >> broker; } else if ( + nextToken == "JMS_USER") { ifsInput >> smqUserName >> password; } else if ( + nextToken == "JMS_QUEUE") { ifsInput >> qName; } else if ( + nextToken == "JMS_TOPIC") { ifsInput >> topicName; } else if (nextToken == "VCELL_USER") { + ifsInput >> vCellUsername; + } else if (nextToken == "SIMULATION_KEY") { + ifsInput >> simKey; + continue; + } else if (nextToken == "JOB_INDEX") { + ifsInput >> jobIndex; + continue; + } + } + + SimulationMessaging::create(broker.c_str(), smqUserName.c_str(), + password.c_str(), qName.c_str(), topicName.c_str(), + vCellUsername.c_str(), simKey, jobIndex, taskID); + #endif +} + +void errExit(int returnCode, std::string &errorMsg) { + #ifdef USE_MESSAGING + if (returnCode != 0) { + if (SimulationMessaging::getInstVar() != nullptr && !SimulationMessaging::getInstVar()->isStopRequested()) { + SimulationMessaging::getInstVar()->setWorkerEvent(new WorkerEvent(JOB_FAILURE, errorMsg.c_str())); + } + } + #endif + + if (returnCode != 0) std::cerr << errorMsg << std::endl; + #ifdef USE_MESSAGING + else if (SimulationMessaging::getInstVar() != nullptr) { + SimulationMessaging::getInstVar()->waitUntilFinished(); + delete SimulationMessaging::getInstVar(); + } + #endif +} From 9418160b4b6f879e132e5569a1b534a9c8117e44 Mon Sep 17 00:00:00 2001 From: Logan Drescher Date: Mon, 30 Jun 2025 09:12:38 -0400 Subject: [PATCH 04/34] Moved input-reading outside of solver --- IDAWin/CMakeLists.txt | 27 +- IDAWin/SteadyStateDriver.cpp | 4 + IDAWin/SteadyStateDriver.h | 8 + IDAWin/SundialsSolverStandalone.cpp | 127 +++---- IDAWin/VCellCVodeSolver.cpp | 70 ++-- IDAWin/VCellCVodeSolver.h | 2 + IDAWin/VCellIDASolver.cpp | 131 ++++--- IDAWin/VCellIDASolver.h | 18 +- IDAWin/VCellSolver.h | 25 ++ IDAWin/VCellSolverFactory.cpp | 514 ++++++++++++++++++++++++++++ IDAWin/VCellSolverFactory.h | 26 ++ IDAWin/VCellSolverInput.cpp | 7 + IDAWin/VCellSolverInput.h | 81 +++++ IDAWin/VCellSundialsSolver.cpp | 336 +++++++++++------- IDAWin/VCellSundialsSolver.h | 266 +++++++------- 15 files changed, 1203 insertions(+), 439 deletions(-) create mode 100644 IDAWin/SteadyStateDriver.cpp create mode 100644 IDAWin/SteadyStateDriver.h create mode 100644 IDAWin/VCellSolver.h create mode 100644 IDAWin/VCellSolverFactory.cpp create mode 100644 IDAWin/VCellSolverFactory.h create mode 100644 IDAWin/VCellSolverInput.cpp create mode 100644 IDAWin/VCellSolverInput.h diff --git a/IDAWin/CMakeLists.txt b/IDAWin/CMakeLists.txt index 76cf35abc..d9480bc26 100644 --- a/IDAWin/CMakeLists.txt +++ b/IDAWin/CMakeLists.txt @@ -1,18 +1,25 @@ project(IDAWin) set (SRC_FILES - OdeResultSet.cpp - StoppedByUserException.cpp - VCellCVodeSolver.cpp - VCellIDASolver.cpp - VCellSundialsSolver.cpp + OdeResultSet.cpp + StoppedByUserException.cpp + VCellCVodeSolver.cpp + VCellIDASolver.cpp + VCellSundialsSolver.cpp + SteadyStateDriver.cpp + VCellSolverFactory.cpp + VCellSolverInput.cpp ) set (HEADER_FILES - OdeResultSet.h - StoppedByUserException.h - VCellCVodeSolver.h - VCellIDASolver.h - VCellSundialsSolver.h + OdeResultSet.h + StoppedByUserException.h + VCellSolverFactory.h + VCellSolver.h + VCellSolverInput.h + VCellSundialsSolver.h + VCellCVodeSolver.h + VCellIDASolver.h + SteadyStateDriver.h ) set (EXE_SRC_FILES diff --git a/IDAWin/SteadyStateDriver.cpp b/IDAWin/SteadyStateDriver.cpp new file mode 100644 index 000000000..297021cce --- /dev/null +++ b/IDAWin/SteadyStateDriver.cpp @@ -0,0 +1,4 @@ +// +// Created by Logan Drescher on 6/25/25. +// +#include "SteadyStateDriver.h" diff --git a/IDAWin/SteadyStateDriver.h b/IDAWin/SteadyStateDriver.h new file mode 100644 index 000000000..d70cdd6f8 --- /dev/null +++ b/IDAWin/SteadyStateDriver.h @@ -0,0 +1,8 @@ +// +// Created by Logan Drescher on 6/25/25. +// + +#ifndef STEADYSTATEDRIVER_H +#define STEADYSTATEDRIVER_H + +#endif //STEADYSTATEDRIVER_H diff --git a/IDAWin/SundialsSolverStandalone.cpp b/IDAWin/SundialsSolverStandalone.cpp index 0c7a95747..07f4ecf8f 100644 --- a/IDAWin/SundialsSolverStandalone.cpp +++ b/IDAWin/SundialsSolverStandalone.cpp @@ -13,6 +13,8 @@ #include "StoppedByUserException.h" #include #include + +#include "VCellSolverFactory.h" #define CVODE_SOLVER "CVODE" #define IDA_SOLVER "IDA" @@ -86,90 +88,47 @@ int parseAndRunWithArgParse(int argc, char *argv[]) { } void activateSolver(std::ifstream& inputFileStream, FILE* outputFile, int taskID) { - std::string solver; - - while (!inputFileStream.eof()) { // Note break statement if "SOLVER" encountered - std::string nextToken; - inputFileStream >> nextToken; - if (nextToken.empty()) continue; - if (nextToken[0] == '#') getline(inputFileStream, nextToken); - else if (nextToken == "JMS_PARAM_BEGIN") { - loadJMSInfo(inputFileStream, taskID); - #ifdef USE_MESSAGING - SimulationMessaging::getInstVar()->start(); // start the thread - #endif - } else if (nextToken == "SOLVER") { - inputFileStream >> solver; - break; - } - } - #ifdef USE_MESSAGING - // should only happen during testing for solver compiled with messaging but run locally. - if (SimulationMessaging::getInstVar() == nullptr) { SimulationMessaging::create(); } - #endif - - if (solver.empty()) { throw "Solver not defined "; } - VCellSundialsSolver *vss = nullptr; - if (solver == IDA_SOLVER) { - vss = new VCellIDASolver(); - } else if (solver == CVODE_SOLVER) { - vss = new VCellCVodeSolver(); - } else { - std::stringstream ss; - ss << "Solver " << solver << " not defined!"; - throw ss.str(); - } - vss->readInput(inputFileStream); - vss->solve(nullptr, true, outputFile, VCellSundialsSolver::checkStopRequested); - - delete vss; -} - -void loadJMSInfo(std::istream &ifsInput, int taskID) { - #ifndef USE_MESSAGING - return; // Only useful for messaging; let's not waste time! - #else - - if (taskID < 0) { - SimulationMessaging::create(); - return; // No need to do any parsing - } - std::string broker; - std::string smqUserName; - std::string password; - std::string qName; - std::string topicName; - std::string vCellUsername; - int simKey, jobIndex; - - while (!ifsInput.eof()) { - std::string nextToken; - ifsInput >> nextToken; - if (nextToken.empty()) continue; - if (nextToken[0] == '#') { - // getline(ifsInput, nextToken); // Is this ignoring because of a comment? - ifsInput.ignore('\n'); - continue; - } - if (nextToken == "JMS_PARAM_END") { ifsInput.ignore(EOF); } else if ( - nextToken == "JMS_BROKER") { ifsInput >> broker; } else if ( - nextToken == "JMS_USER") { ifsInput >> smqUserName >> password; } else if ( - nextToken == "JMS_QUEUE") { ifsInput >> qName; } else if ( - nextToken == "JMS_TOPIC") { ifsInput >> topicName; } else if (nextToken == "VCELL_USER") { - ifsInput >> vCellUsername; - } else if (nextToken == "SIMULATION_KEY") { - ifsInput >> simKey; - continue; - } else if (nextToken == "JOB_INDEX") { - ifsInput >> jobIndex; - continue; - } - } - - SimulationMessaging::create(broker.c_str(), smqUserName.c_str(), - password.c_str(), qName.c_str(), topicName.c_str(), - vCellUsername.c_str(), simKey, jobIndex, taskID); - #endif + // std::string solver; + // + // while (!inputFileStream.eof()) { // Note break statement if "SOLVER" encountered + // std::string nextToken; + // inputFileStream >> nextToken; + // if (nextToken.empty()) continue; + // if (nextToken[0] == '#') getline(inputFileStream, nextToken); + // else if (nextToken == "JMS_PARAM_BEGIN") { + // loadJMSInfo(inputFileStream, taskID); + // #ifdef USE_MESSAGING + // SimulationMessaging::getInstVar()->start(); // start the thread + // #endif + // } else if (nextToken == "SOLVER") { + // inputFileStream >> solver; + // break; + // } + // } + // #ifdef USE_MESSAGING + // // should only happen during testing for solver compiled with messaging but run locally. + // if (SimulationMessaging::getInstVar() == nullptr) { SimulationMessaging::create(); } + // #endif + // + // if (solver.empty()) { throw "Solver not defined "; } + // VCellSundialsSolver *vss = nullptr; + // + // if (solver == IDA_SOLVER) { + // vss = new VCellIDASolver(); + // } else if (solver == CVODE_SOLVER) { + // vss = new VCellCVodeSolver(); + // } else { + // std::stringstream ss; + // ss << "Solver " << solver << " not defined!"; + // throw ss.str(); + // } + // + // vss->solve(nullptr, true, outputFile, VCellSundialsSolver::checkStopRequested); + // delete vss; + + + VCellSolver* targetSolver = VCellSolverFactory::produceVCellSolver(inputFileStream, taskID); + targetSolver->solve(nullptr, true, outputFile, VCellSundialsSolver::checkStopRequested); } void errExit(int returnCode, std::string &errorMsg) { diff --git a/IDAWin/VCellCVodeSolver.cpp b/IDAWin/VCellCVodeSolver.cpp index f81ed0a59..e37b7d418 100644 --- a/IDAWin/VCellCVodeSolver.cpp +++ b/IDAWin/VCellCVodeSolver.cpp @@ -9,20 +9,16 @@ #include #include #include - -#ifdef USE_MESSAGING -#include -#endif - #include - #include /* prototypes for CVODE fcts. and consts. */ #include /* serial N_Vector types, fcts., and macros */ #include /* prototype for CVDense */ //#include /* prototype for CVSPGMR */ #include /* definitions DenseMat DENSE_ELEM */ #include /* definition of type realtype */ - +#ifdef USE_MESSAGING +#include +#endif #define ToleranceType CV_SS /** @@ -151,13 +147,13 @@ VCellCVodeSolver::VCellCVodeSolver() : VCellSundialsSolver() { VCellCVodeSolver::~VCellCVodeSolver() { CVodeFree(&solver); - for (int i = 0; i < NEQ; i ++) { + for (int i = 0; i < NUM_EQUATIONS; i ++) { delete rateExpressions[i]; } delete[] rateExpressions; } -/* +/**---------------------------------------------------- Input format: STARTING_TIME 0.0 ENDING_TIME 0.1 @@ -172,15 +168,15 @@ Input format: RATE ((20.0 * x_o * D_B0) - (50.0 * x_i)); ODE x_o INIT 0.0; RATE ( - ((20.0 * x_o * D_B0) - (50.0 * x_i)) + (1505000.0 * (3.322259136212625E-4 - (3.322259136212625E-4 * x_o) - (3.322259136212625E-4 * x_i))) - (100.0 * x_o)); -*/ +--------------------------------------------------------------*/ void VCellCVodeSolver::readEquations(std::istream& inputstream) { try { std::string name; std::string exp; - rateExpressions = new Expression*[NEQ]; + rateExpressions = new Expression*[NUM_EQUATIONS]; - for (int i = 0; i < NEQ; i ++) { + for (int i = 0; i < NUM_EQUATIONS; i ++) { // ODE inputstream >> name >> variableNames[i]; @@ -209,11 +205,35 @@ void VCellCVodeSolver::readEquations(std::istream& inputstream) { } } +void VCellCVodeSolver::readEquations(VCellSolverInputBreakdown& inputBreakdown) { + try { + for (int i = 0; i < NUM_EQUATIONS; i ++) { + variableNames[i] = inputBreakdown.modelSettings.VARIABLE_NAMES[i]; + try { + initialConditionExpressions[i] = new Expression(inputBreakdown.modelSettings.INITIAL_CONDITION_EXPRESSIONS[i]); + } catch (VCell::Exception& ex) { + throw VCell::Exception(std::string("Initial condition expression for [") + variableNames[i] + "] " + ex.getMessage()); + } + } + + rateExpressions = new Expression*[inputBreakdown.modelSettings.RATE_EXPRESSIONS.size()]; + for (int i = 0; i < inputBreakdown.modelSettings.RATE_EXPRESSIONS.size(); i++) { + rateExpressions[i] = new Expression(inputBreakdown.modelSettings.RATE_EXPRESSIONS[i]); + } + } catch (char* ex) { + throw VCell::Exception(std::string("VCellCVodeSolver::readInput() : ") + ex); + } catch (VCell::Exception& ex) { + throw VCell::Exception(std::string("VCellCVodeSolver::readInput() : ") + ex.getMessage()); + } catch (...) { + throw "VCellCVodeSolver::readInput() : caught unknown exception"; + } +} + void VCellCVodeSolver::initialize() { VCellSundialsSolver::initialize(); // rate can be function of variables, parameters and discontinuities. - for (int i = 0; i < NEQ; i ++) { + for (int i = 0; i < NUM_EQUATIONS; i ++) { rateExpressions[i]->bindExpression(defaultSymbolTable); } } @@ -222,7 +242,7 @@ int VCellCVodeSolver::RHS (realtype t, N_Vector y, N_Vector r) { try { updateTandVariableValues(t, y); double* r_data = NV_DATA_S(r); - for (int i = 0; i < NEQ; i ++) { + for (int i = 0; i < NUM_EQUATIONS; i ++) { r_data[i] = rateExpressions[i]->evaluateVector(values); } recoverableErrMsg = ""; @@ -264,9 +284,9 @@ void VCellCVodeSolver::solve(double* paramValues, bool bPrintProgress, FILE* out this->odeResultSet->clearData(); // copy parameter values to the end of values, these will stay the same during solving - memset(values, 0, (NEQ + 1) * sizeof(double)); - memcpy(values + 1 + NEQ, paramValues, NPARAM * sizeof(double)); - memset(values + 1 + NEQ + NPARAM, 0, numDiscontinuities * sizeof(double)); + memset(values, 0, (NUM_EQUATIONS + 1) * sizeof(double)); + memcpy(values + 1 + NUM_EQUATIONS, paramValues, NUM_PARAMETERS * sizeof(double)); + memset(values + 1 + NUM_EQUATIONS + NUM_PARAMETERS, 0, numDiscontinuities * sizeof(double)); initCVode(paramValues); cvodeSolve(bPrintProgress, outputFile, checkStopRequested); @@ -274,7 +294,7 @@ void VCellCVodeSolver::solve(double* paramValues, bool bPrintProgress, FILE* out void VCellCVodeSolver::initCVode(double* paramValues) { //Initialize y, variable portion of values - for (int i = 0; i < NEQ; i ++) { + for (int i = 0; i < NUM_EQUATIONS; i ++) { NV_Ith_S(y, i) = initialConditionExpressions[i]->evaluateVector(paramValues); } @@ -305,7 +325,7 @@ void VCellCVodeSolver::reInit(double t) { flag = CVodeSetFdata(solver, this); checkCVodeFlag(flag); - flag = CVDense(solver, NEQ); + flag = CVDense(solver, NUM_EQUATIONS); //flag = CVSpgmr(solver, PREC_NONE, 0); checkCVodeFlag(flag); @@ -317,8 +337,8 @@ void VCellCVodeSolver::reInit(double t) { } bool VCellCVodeSolver::fixInitialDiscontinuities(double t) { - double* oldy = new double[NEQ]; - memcpy(oldy, NV_DATA_S(y), NEQ * sizeof(realtype)); + double* oldy = new double[NUM_EQUATIONS]; + memcpy(oldy, NV_DATA_S(y), NUM_EQUATIONS * sizeof(realtype)); double epsilon = std::max(1e-15, ENDING_TIME * 1e-10); double currentTime = t; @@ -341,11 +361,11 @@ bool VCellCVodeSolver::fixInitialDiscontinuities(double t) { } } if (bInitChanged) { - memcpy(values + 1 + NEQ + NPARAM, discontinuityValues, numDiscontinuities * sizeof(double)); + memcpy(values + 1 + NUM_EQUATIONS + NUM_PARAMETERS, discontinuityValues, numDiscontinuities * sizeof(double)); } //revert y - memcpy(NV_DATA_S(y), oldy, NEQ * sizeof(realtype)); + memcpy(NV_DATA_S(y), oldy, NUM_EQUATIONS * sizeof(realtype)); reInit(t); delete[] oldy; @@ -427,7 +447,7 @@ void VCellCVodeSolver::cvodeSolve(bool bPrintProgress, FILE* outputFile, void (* if (returnCode == CV_ROOT_RETURN || iterationCount % keepEvery == 0 || Time >= ENDING_TIME){ outputCount++; - if (outputCount * (NEQ + 1) * bytesPerSample > MaxFileSizeBytes){ + if (outputCount * (NUM_EQUATIONS + 1) * bytesPerSample > MaxFileSizeBytes){ /* if more than one gigabyte, then fail */ const std::string msg{"output exceeded maximum " + std::to_string(MaxFileSizeBytes) + " bytes"}; throw VCell::Exception(msg.c_str()); @@ -487,5 +507,5 @@ void VCellCVodeSolver::cvodeSolve(bool bPrintProgress, FILE* outputFile, void (* void VCellCVodeSolver::updateTandVariableValues(realtype t, N_Vector y) { values[0] = t; - memcpy(values + 1, NV_DATA_S(y), NEQ * sizeof(realtype)); + memcpy(values + 1, NV_DATA_S(y), NUM_EQUATIONS * sizeof(realtype)); } diff --git a/IDAWin/VCellCVodeSolver.h b/IDAWin/VCellCVodeSolver.h index f9cceb84a..85d9decc4 100644 --- a/IDAWin/VCellCVodeSolver.h +++ b/IDAWin/VCellCVodeSolver.h @@ -14,8 +14,10 @@ class VCellCVodeSolver : public VCellSundialsSolver { protected: void readEquations(std::istream& inputstream) override; + void readEquations(VCellSolverInputBreakdown& inputBreakdown) override; void initialize() override; std::string getSolverName() override { return "CVODE"; } + VCellSolverTypes getSolverType() override { return VCellSolverTypes::CVODE; } private: VCell::Expression** rateExpressions; diff --git a/IDAWin/VCellIDASolver.cpp b/IDAWin/VCellIDASolver.cpp index 4a4fce35c..4d45a1a7f 100644 --- a/IDAWin/VCellIDASolver.cpp +++ b/IDAWin/VCellIDASolver.cpp @@ -9,20 +9,16 @@ #include #include #include - - -#ifdef USE_MESSAGING -#include -#endif - #include #include #include #include //#include #include - #include +#ifdef USE_MESSAGING +#include +#endif /** * calling sequence @@ -214,7 +210,7 @@ VCellIDASolver::~VCellIDASolver() { N_VDestroy_Serial(yp); N_VDestroy_Serial(id); - for (int i = 0; i < NEQ; i ++) { + for (int i = 0; i < NUM_EQUATIONS; i ++) { delete rhsExpressions[i]; delete[] transformMatrix[i]; delete[] inverseTransformMatrix[i]; @@ -305,7 +301,7 @@ void VCellIDASolver::readEquations(std::istream& inputstream) { if (token != "VAR"){ throw "expecting VAR"; } - for (int i = 0; i < NEQ; i ++) { + for (int i = 0; i < NUM_EQUATIONS; i ++) { // consume "VAR" token, but first line has it's "VAR" already consumed if (i > 0){ inputstream >> token; @@ -323,28 +319,28 @@ void VCellIDASolver::readEquations(std::istream& inputstream) { } //TRANSFORM - transformMatrix = new double*[NEQ]; + transformMatrix = new double*[NUM_EQUATIONS]; inputstream >> token; if (token != "TRANSFORM") { throw "expecting TRANSFORM"; } getline(inputstream, exp); // go to next line - for (int i = 0; i < NEQ; i ++) { - transformMatrix[i] = new double[NEQ]; - for (int j = 0; j < NEQ; j ++) { + for (int i = 0; i < NUM_EQUATIONS; i ++) { + transformMatrix[i] = new double[NUM_EQUATIONS]; + for (int j = 0; j < NUM_EQUATIONS; j ++) { inputstream >> transformMatrix[i][j]; } } //INVERSETRANSFORM - inverseTransformMatrix = new double*[NEQ]; + inverseTransformMatrix = new double*[NUM_EQUATIONS]; inputstream >> token; if (token != "INVERSETRANSFORM") { throw "expecting INVERSETRANSFORM"; } getline(inputstream, exp); // go to next line - for (int i = 0; i < NEQ; i ++) { - inverseTransformMatrix[i] = new double[NEQ]; - for (int j = 0; j < NEQ; j ++) { + for (int i = 0; i < NUM_EQUATIONS; i ++) { + inverseTransformMatrix[i] = new double[NUM_EQUATIONS]; + for (int j = 0; j < NUM_EQUATIONS; j ++) { inputstream >> inverseTransformMatrix[i][j]; } } @@ -364,13 +360,13 @@ void VCellIDASolver::readEquations(std::istream& inputstream) { throw "expecting ALGEBRAIC"; } inputstream >> numAlgebraic; - if (numDifferential + numAlgebraic != NEQ) { + if (numDifferential + numAlgebraic != NUM_EQUATIONS) { throw "numDifferential + numAlgebraic != NEQ"; } getline(inputstream, exp); // go to next line - rhsExpressions = new Expression*[NEQ]; - for (int i = 0; i < NEQ; i ++) { + rhsExpressions = new Expression*[NUM_EQUATIONS]; + for (int i = 0; i < NUM_EQUATIONS; i ++) { try { rhsExpressions[i] = readExpression(inputstream); } catch (VCell::Exception& ex) { @@ -388,15 +384,61 @@ void VCellIDASolver::readEquations(std::istream& inputstream) { } } +void VCellIDASolver::readEquations(VCellSolverInputBreakdown& inputBreakdown) { + try { + for (int i = 0; i < NUM_EQUATIONS; i ++) { + variableNames[i] = inputBreakdown.modelSettings.VARIABLE_NAMES[i]; + try { + initialConditionExpressions[i] = new Expression(inputBreakdown.modelSettings.INITIAL_CONDITION_EXPRESSIONS[i]); + } catch (VCell::Exception& ex) { + throw VCell::Exception(std::string("Initial condition expression for [") + variableNames[i] + "] " + ex.getMessage()); + } + } + transformMatrix = new double*[NUM_EQUATIONS]; + for (int i = 0; i < NUM_EQUATIONS; i ++) { + transformMatrix[i] = new double[NUM_EQUATIONS]; + for (int j = 0; j < NUM_EQUATIONS; j ++) { + transformMatrix[i][j] = std::stod(inputBreakdown.modelSettings.FLAT_TRANSFORM_MATRIX[i * NUM_EQUATIONS + j]); + } + } + + inverseTransformMatrix = new double*[NUM_EQUATIONS]; + for (int i = 0; i < NUM_EQUATIONS; i ++) { + inverseTransformMatrix[i] = new double[NUM_EQUATIONS]; + for (int j = 0; j < NUM_EQUATIONS; j ++) { + inverseTransformMatrix[i][j] = std::stod(inputBreakdown.modelSettings.FLAT_INVERSE_TRANSFORM_MATRIX[i * NUM_EQUATIONS + j]); + } + } + numDifferential = inputBreakdown.modelSettings.NUM_DIFFERENTIAL; + numAlgebraic = inputBreakdown.modelSettings.NUM_ALGEBRAIC; + rhsExpressions = new Expression*[NUM_EQUATIONS]; + for (int i = 0; i < NUM_EQUATIONS; i ++) { + try { + rhsExpressions[i] = new Expression(inputBreakdown.modelSettings.RATE_EXPRESSIONS[i]); + } catch (VCell::Exception& ex) { + std::stringstream ss; + ss << "RHS[" << i << "] " << ex.getMessage(); + throw VCell::Exception(ss.str()); + } + } + } catch (const char* ex) { + throw VCell::Exception(std::string("VCellIDASolver::readInput() : ") + ex); + } catch (VCell::Exception& ex) { + throw VCell::Exception(std::string("VCellIDASolver::readInput() : ") + ex.getMessage()); + } catch (...) { + throw VCell::Exception("VCellIDASolver::readInput() caught unknown exception"); + } +} + void VCellIDASolver::initialize() { VCellSundialsSolver::initialize(); - for (int i = 0; i < NEQ; i ++) { + for (int i = 0; i < NUM_EQUATIONS; i ++) { rhsExpressions[i]->bindExpression(defaultSymbolTable); } - yp = N_VNew_Serial(NEQ); - id = N_VNew_Serial(NEQ); + yp = N_VNew_Serial(NUM_EQUATIONS); + id = N_VNew_Serial(NUM_EQUATIONS); if (yp == 0 || id == 0) { throw "Out of Memory"; @@ -416,9 +458,9 @@ void VCellIDASolver::solve(double* paramValues, bool bPrintProgress, FILE* outpu // copy parameter values to the end of values, these will stay the same during solving // values[0] is time, y values will be copied to 1~NEQ of values in residual function - memset(values, 0, (NEQ + 1 + NPARAM) * sizeof(double)); - memcpy(values + NEQ + 1, paramValues, NPARAM * sizeof(double)); - memset(values + 1 + NEQ + NPARAM, 0, numDiscontinuities * sizeof(double)); + memset(values, 0, (NUM_EQUATIONS + 1 + NUM_PARAMETERS) * sizeof(double)); + memcpy(values + NUM_EQUATIONS + 1, paramValues, NUM_PARAMETERS * sizeof(double)); + memset(values + 1 + NUM_EQUATIONS + NUM_PARAMETERS, 0, numDiscontinuities * sizeof(double)); initIDA(paramValues); idaSolve(bPrintProgress, outputFile, checkStopRequested); @@ -431,18 +473,18 @@ void VCellIDASolver::solve(double* paramValues, bool bPrintProgress, FILE* outpu void VCellIDASolver::initIDA(double* paramValues) { // compute initial condition - double* initCond = new double[NEQ]; - for (int i = 0; i < NEQ; i ++) { + double* initCond = new double[NUM_EQUATIONS]; + for (int i = 0; i < NUM_EQUATIONS; i ++) { initCond[i] = initialConditionExpressions[i]->evaluateVector(paramValues); } // must initialize y and yp before call IDAMalloc // Initialize y, yp and id. transform initial condition to y - for (int i = 0; i < NEQ; i ++) { + for (int i = 0; i < NUM_EQUATIONS; i ++) { NV_Ith_S(id, i) = i < numDifferential ? RCONST(1) : RCONST(0); NV_Ith_S(yp, i) = 0; // Initialize yp to be 0, they will be reinitialized later. NV_Ith_S(y, i) = 0; - for (int j = 0; j < NEQ; j ++) { + for (int j = 0; j < NUM_EQUATIONS; j ++) { NV_Ith_S(y, i) += transformMatrix[i][j] * initCond[j]; } } @@ -481,7 +523,7 @@ void VCellIDASolver::reInit(double t) { checkIDAFlag(flag); // choose the linear solver (Dense "direct" matrix LU decomposition solver). - flag = IDADense(solver, NEQ); + flag = IDADense(solver, NUM_EQUATIONS); //flag = IDASpgmr(solver, 0); checkIDAFlag(flag); @@ -507,8 +549,8 @@ bool VCellIDASolver::fixInitialDiscontinuities(double t) { int flag = IDARootInit(solver, 0, RootFn_callback, this); checkIDAFlag(flag); - double* oldy = new double[NEQ]; - memcpy(oldy, NV_DATA_S(y), NEQ * sizeof(realtype)); + double* oldy = new double[NUM_EQUATIONS]; + memcpy(oldy, NV_DATA_S(y), NUM_EQUATIONS * sizeof(realtype)); double epsilon = std::max(1e-15, ENDING_TIME * 1e-8); double currentTime = t; @@ -530,12 +572,12 @@ bool VCellIDASolver::fixInitialDiscontinuities(double t) { } } if (bInitChanged) { - memcpy(values + 1 + NEQ + NPARAM, discontinuityValues, numDiscontinuities * sizeof(double)); + memcpy(values + 1 + NUM_EQUATIONS + NUM_PARAMETERS, discontinuityValues, numDiscontinuities * sizeof(double)); } //revert y and yp - memcpy(NV_DATA_S(y), oldy, NEQ * sizeof(realtype)); - memset(NV_DATA_S(yp), 0, NEQ * sizeof(realtype)); + memcpy(NV_DATA_S(y), oldy, NUM_EQUATIONS * sizeof(realtype)); + memset(NV_DATA_S(yp), 0, NUM_EQUATIONS * sizeof(realtype)); reInit(t); flag = IDARootInit(solver, 2*numDiscontinuities, RootFn_callback, this); @@ -622,11 +664,10 @@ void VCellIDASolver::idaSolve(bool bPrintProgress, FILE* outputFile, void (*chec if (returnCode == IDA_ROOT_RETURN || iterationCount % keepEvery == 0 || Time >= ENDING_TIME){ outputCount ++; - if (outputCount *(NEQ + 1) * bytesPerSample > MaxFileSizeBytes){ - /* if more than one gigabyte, then fail */ - char msg[100]; - sprintf(msg, "output exceeded %ld bytes\n", MaxFileSizeBytes); - throw VCell::Exception(msg); + if (outputCount *(NUM_EQUATIONS + 1) * bytesPerSample > MaxFileSizeBytes){ + /* if more than one gigabyte, then fail */ + std::string problem = std::format("output exceeded {} bytes\n", MaxFileSizeBytes); + throw VCell::Exception(problem); } writeData(Time, outputFile); if (bPrintProgress) { @@ -685,9 +726,9 @@ void VCellIDASolver::idaSolve(bool bPrintProgress, FILE* outputFile, void (*chec // override updateTempRowData, since y values need to be transformed to the original variables. void VCellIDASolver::updateTempRowData(double currTime) { tempRowData[0] = currTime; - for (int i = 0; i < NEQ; i ++) { + for (int i = 0; i < NUM_EQUATIONS; i ++) { tempRowData[i + 1] = 0; - for (int j = 0; j < NEQ; j ++) { + for (int j = 0; j < NUM_EQUATIONS; j ++) { tempRowData[i + 1] += inverseTransformMatrix[i][j] * NV_Ith_S(y, j); } } @@ -695,9 +736,9 @@ void VCellIDASolver::updateTempRowData(double currTime) { void VCellIDASolver::updateTandVariableValues(realtype t, N_Vector y) { values[0] = t; - for (int i = 0; i < NEQ; i ++) { + for (int i = 0; i < NUM_EQUATIONS; i ++) { values[i + 1] = 0; - for (int j = 0; j < NEQ; j ++) { + for (int j = 0; j < NUM_EQUATIONS; j ++) { values[i + 1] += inverseTransformMatrix[i][j] * NV_Ith_S(y, j); } } diff --git a/IDAWin/VCellIDASolver.h b/IDAWin/VCellIDASolver.h index 459701003..0ba22673f 100644 --- a/IDAWin/VCellIDASolver.h +++ b/IDAWin/VCellIDASolver.h @@ -7,15 +7,17 @@ class VCellIDASolver : public VCellSundialsSolver { public: VCellIDASolver(); - ~VCellIDASolver(); + ~VCellIDASolver() override; - void solve(double* paramValues=0, bool bPrintProgress=false, FILE* outputFile=0, void (*checkStopRequested)(double, long)=0); + void solve(double* paramValues=0, bool bPrintProgress=false, FILE* outputFile=0, void (*checkStopRequested)(double, long)=0) override; protected: - void updateTempRowData(double currTime); - void readEquations(std::istream& inputstream); - void initialize(); - std::string getSolverName() { return "IDA"; } + void updateTempRowData(double currTime) override; + void readEquations(std::istream& inputstream) override; + void readEquations(VCellSolverInputBreakdown& inputBreakdown) override; + void initialize() override; + std::string getSolverName() override { return "IDA"; } + VCellSolverTypes getSolverType() override { return VCellSolverTypes::IDA; } private: VCell::Expression** rhsExpressions; // can be rate expression in ODE case or RHS expression in DAE case @@ -60,8 +62,8 @@ class VCellIDASolver : public VCellSundialsSolver { void checkIDAFlag(int flag); void reInit(realtype t); - bool fixInitialDiscontinuities(double t); - void updateTandVariableValues(realtype t, N_Vector y); + bool fixInitialDiscontinuities(double t) override; + void updateTandVariableValues(realtype t, N_Vector y) override; void onIDAReturn(realtype Time, int returnCode); }; diff --git a/IDAWin/VCellSolver.h b/IDAWin/VCellSolver.h new file mode 100644 index 000000000..1d95dc705 --- /dev/null +++ b/IDAWin/VCellSolver.h @@ -0,0 +1,25 @@ +// +// Created by Logan Drescher on 6/26/25. +// + +#ifndef VCELLSOLVER_H +#define VCELLSOLVER_H +#include "VCellSolverInput.h" + +/** + * Interfacing class for all solvers; we generally only care about configuring and running... + */ +class VCellSolver { + public: + virtual ~VCellSolver() = default; + + virtual void configureFromInput(VCellSolverInputBreakdown& inputBreakdown) = 0; + + virtual void solve(double *paramValues = nullptr, bool bPrintProgress = false, FILE *outputFile = nullptr, + void (*checkStopRequested)(double, long) = nullptr) = 0; + + protected: + VCellSolver() = default; +}; + +#endif //VCELLSOLVER_H diff --git a/IDAWin/VCellSolverFactory.cpp b/IDAWin/VCellSolverFactory.cpp new file mode 100644 index 000000000..5a905d73e --- /dev/null +++ b/IDAWin/VCellSolverFactory.cpp @@ -0,0 +1,514 @@ +// +// Created by Logan Drescher on 6/26/25. +// +#include "VCellSolverFactory.h" +#include "VCellSundialsSolver.h" +#include +#include +#include +#include +#include +#include + +#include "Exception.h" +#include "VCellCVodeSolver.h" +#include "VCellIDASolver.h" +#ifdef USE_MESSAGING +static void loadJMSInfo(std::istream &ifsInput, int taskID); +#endif +// Forward Declarations +static void readDiscontinuities (std::istream &inputStream, VCellSolverInputBreakdown& inputBreakdown); +static void readEvents(std::istream &inputStream, VCellSolverInputBreakdown& inputBreakdown); +static std::string getExpressionString(std::istream& inputStream); +static std::string getExpressionString(std::string& unparsedString); +static void trimString(std::string &str); +// static VCell::Expression* readExpression(std::istream &inputstream); +static void collectCVodeEquations(std::ifstream& inputFileStream, VCellSolverInputBreakdown& inputBreakdown); +static void collectIDAEquations(std::ifstream& inputFileStream, VCellSolverInputBreakdown& inputBreakdown); +static void collectSteadyStateTerms(std::ifstream& inputFileStream, VCellSolverInputBreakdown& inputBreakdown); +static void loadJMSInfo(std::istream &ifsInput, int taskID); + +VCellSolver* VCellSolverFactory::produceVCellSolver(std::ifstream& inputFileStream, int taskID){ + VCellSolverInputBreakdown inputBreakdown = parseInputFile(inputFileStream, taskID); + VCellSolver* desiredSolver; + switch (inputBreakdown.solverType) { + case VCellSolverTypes::CVODE: + desiredSolver = new VCellCVodeSolver(); + break; + case VCellSolverTypes::IDA: + desiredSolver = new VCellIDASolver(); + break; + // case VCellSolverTypes::STEADY_STATE: + // break; + default: + throw new VCell::Exception("Unknown VCellSolver type"); + } + desiredSolver->configureFromInput(inputBreakdown); + return desiredSolver; +} + +VCellSolverInputBreakdown VCellSolverFactory::parseInputFile(std::ifstream& inputFileStream, int taskID) { + VCellSolverInputBreakdown inputBreakdown; + while (!inputFileStream.eof()) { + std::string nextToken; + inputFileStream >> nextToken; + if (nextToken.empty()) continue; + if (nextToken[0] == '#') std::getline(inputFileStream, nextToken); + else if (nextToken == "JMS_PARAM_BEGIN") { + #ifdef USE_MESSAGING + loadJMSInfo(inputFileStream, taskID); + SimulationMessaging::getInstVar()->start(); // start the thread + #endif + } else if (nextToken == "SOLVER") { + std::string solverName; + inputFileStream >> solverName; + inputBreakdown.solverType = determineSolverType(solverName); + } else if (nextToken == "JMS_PARAM_BEGIN") { + loadJMSInfo(inputFileStream, taskID); + #ifdef USE_MESSAGING + SimulationMessaging::getInstVar()->start(); // start the thread + #endif + } + else if (nextToken == "STARTING_TIME") { inputFileStream >> inputBreakdown.timeCourseSettings.STARTING_TIME; } + else if (nextToken == "ENDING_TIME") { inputFileStream >> inputBreakdown.timeCourseSettings.ENDING_TIME; } + else if (nextToken == "RELATIVE_TOLERANCE") { inputFileStream >> inputBreakdown.timeCourseSettings.RELATIVE_TOLERANCE; } + else if (nextToken == "ABSOLUTE_TOLERANCE") { inputFileStream >> inputBreakdown.timeCourseSettings.ABSOLUTE_TOLERANCE; } + else if (nextToken == "MAX_TIME_STEP") { inputFileStream >> inputBreakdown.timeCourseSettings.MAX_TIME_STEP; } + else if (nextToken == "KEEP_EVERY") { inputFileStream >> inputBreakdown.timeCourseSettings.KEEP_EVERY; } + else if (nextToken == "OUTPUT_TIME_STEP"){ inputFileStream >> inputBreakdown.timeCourseSettings.OUTPUT_TIME_STEP; } + else if (nextToken == "OUTPUT_TIMES") { + std::size_t numOfTimePoints; + inputFileStream >> numOfTimePoints; + double timePoint; + for (std::size_t i = 0; i < numOfTimePoints; i++) { + inputFileStream >> timePoint; + const bool isValidTimePoint = timePoint > inputBreakdown.timeCourseSettings.STARTING_TIME + && timePoint <= inputBreakdown.timeCourseSettings.ENDING_TIME; + if (isValidTimePoint) inputBreakdown.timeCourseSettings.TIME_POINTS.push_back(timePoint); + } + if (inputBreakdown.timeCourseSettings.TIME_POINTS.back() < inputBreakdown.timeCourseSettings.ENDING_TIME) + inputBreakdown.timeCourseSettings.TIME_POINTS.push_back(inputBreakdown.timeCourseSettings.ENDING_TIME); + } else if (nextToken == "DISCONTINUITIES") { + readDiscontinuities(inputFileStream, inputBreakdown); + } else if (nextToken == "NUM_PARAMETERS") { + std::size_t numParameters; + inputFileStream >> numParameters; + for (std::size_t i = 0; i < numParameters; i++) { + std::string name; + inputFileStream >> name; + inputBreakdown.modelSettings.PARAMETER_NAMES.push_back(name); + } + } else if (nextToken == "NUM_EQUATIONS") { VCellSolverFactory::processEquations(inputFileStream, inputBreakdown); } + else if (nextToken == "EVENTS") { readEvents(inputFileStream, inputBreakdown); } + else if (nextToken == "STEADY_STATE") { + std::string confirmation; + inputFileStream >> confirmation; + if (confirmation == "TRUE") { collectSteadyStateTerms(inputFileStream, inputBreakdown); } + } + else throw VCell::Exception("Unexpected token \"" + nextToken + "\" in the input file!"); + + } + return inputBreakdown; +} + +VCellSolverTypes VCellSolverFactory::determineSolverType(const std::string& solverName){ + if (solverName == "CVODE") return VCellSolverTypes::CVODE; + if (solverName == "IDA") return VCellSolverTypes::IDA; + if (solverName == "CSSS") return VCellSolverTypes::STEADY_STATE; // copasi-style steady-state + throw new std::runtime_error("Unknown solver type: `" + solverName + "`"); +} + +void VCellSolverFactory::processEquations(std::ifstream& inputFileStream, VCellSolverInputBreakdown& inputBreakdown) { + if (inputBreakdown.solverType == VCellSolverTypes::IDA) collectIDAEquations(inputFileStream, inputBreakdown); + else collectCVodeEquations(inputFileStream, inputBreakdown); +} + +/** * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Local Helper Functions + * * * * * * * * * * * * * * * * * * * * * * * * * * * **/ + +#ifdef USE_MESSAGING +static void loadJMSInfo(std::istream &ifsInput, int taskID) { + if (taskID < 0) { + SimulationMessaging::create(); + return; // No need to do any parsing + } + std::string broker; + std::string smqUserName; + std::string password; + std::string qName; + std::string topicName; + std::string vCellUsername; + int simKey, jobIndex; + + while (!ifsInput.eof()) { + std::string nextToken; + ifsInput >> nextToken; + if (nextToken.empty()) continue; + if (nextToken[0] == '#') { + // std::getline(ifsInput, nextToken); // Is this ignoring because of a comment? + ifsInput.ignore('\n'); + continue; + } + if (nextToken == "JMS_PARAM_END") { ifsInput.ignore(EOF); } else if ( + nextToken == "JMS_BROKER") { ifsInput >> broker; } else if ( + nextToken == "JMS_USER") { ifsInput >> smqUserName >> password; } else if ( + nextToken == "JMS_QUEUE") { ifsInput >> qName; } else if ( + nextToken == "JMS_TOPIC") { ifsInput >> topicName; } else if (nextToken == "VCELL_USER") { + ifsInput >> vCellUsername; + } else if (nextToken == "SIMULATION_KEY") { + ifsInput >> simKey; + continue; + } else if (nextToken == "JOB_INDEX") { + ifsInput >> jobIndex; + continue; + } + } + + SimulationMessaging::create(broker.c_str(), smqUserName.c_str(), + password.c_str(), qName.c_str(), topicName.c_str(), + vCellUsername.c_str(), simKey, jobIndex, taskID); + +} +#endif + +static void readDiscontinuities(std::istream &inputStream, VCellSolverInputBreakdown& inputBreakdown) { + VCellSolverInputBreakdown::DiscontinuityComponents discontinuityComponents; + std::size_t numOfDiscontinuities; + inputStream >> numOfDiscontinuities; + + for (std::size_t i = 0; i < numOfDiscontinuities; i++) { + inputStream >> discontinuityComponents.symbol; + + std::string line = ""; + std::getline(inputStream, line); + std::string::size_type pos = line.find(";"); + if (pos == std::string::npos) { + std::string msg = std::string("discontinuity expression ") + BAD_EXPRESSION_MSG; + throw VCell::Exception(msg); + } + std::string exp = line.substr(0, pos + 1); + trimString(exp); + discontinuityComponents.discontinuityExpression = new VCell::Expression(exp); + + exp = line.substr(pos + 1); + discontinuityComponents.rootFindingExpression = new VCell::Expression(getExpressionString(exp)); + + inputBreakdown.discontinuitiesSettings.DISCONTINUITIES.push_back(std::move(discontinuityComponents)); + } +} + +static void readEvents(std::istream &inputStream, VCellSolverInputBreakdown& inputBreakdown) { + std::size_t numOfEvents; + std::string token; + + inputStream >> numOfEvents; + //events = new Event *[numEvents]; + for (std::size_t i = 0; i < numOfEvents; i++) { + VCellSolverInputBreakdown::EventComponents eventComponents; + while (true) { // Break on "EVENTASSIGNMENTS" + inputStream >> token; + if (token == "EVENT") { inputStream >> eventComponents.eventName; } + else if (token == "TRIGGER") { + try { + eventComponents.triggerExpression = getExpressionString(inputStream); + } catch (VCell::Exception &ex) { + throw VCell::Exception(std::string("trigger expression") + " " + ex.getMessage()); + } + } else if (token == "DELAY") { + inputStream >> token; + eventComponents.shouldUseValuesAtTriggerTime = (token == "true"); + try { + eventComponents.delayDurationExpression = getExpressionString(inputStream); + } catch (VCell::Exception &ex) { + throw VCell::Exception(std::string("delay duration expression") + " " + ex.getMessage()); + } + } else if (token == "EVENTASSIGNMENTS") { + int numEventAssignments; + inputStream >> numEventAssignments; + for (int j = 0; j < numEventAssignments; j++) { + std::pair eventAssignment; + inputStream >> eventAssignment.first; //varIndex + try { + eventAssignment.second = getExpressionString(inputStream); // assignment expression + } catch (VCell::Exception &ex) { + throw VCell::Exception(std::string("event assignment expression") + " " + ex.getMessage()); + } + eventComponents.eventAssignments.push_back(std::move(eventAssignment)); + //eventComponents.eventAssignments.emplace_back(varIndex, assignmentExpression); // should try this in the future, more descriptive + } + break; + } else { throw VCell::Exception("Unexpected token \"" + token + "\" in the input file!"); } + } + inputBreakdown.eventSettings.EVENTS.push_back(std::move(eventComponents)); + } +} + +static void trimString(std::string &str) { + if (std::string::size_type pos = str.find_last_not_of(" \r\n"); pos != std::string::npos) { + str.erase(pos + 1); + pos = str.find_first_not_of(" \r\n"); + if (pos != std::string::npos) { str.erase(0, pos); } + } else { str.erase(str.begin(), str.end()); } +} + +static std::string getExpressionString(std::istream& inputStream) { + std::string exp; + std::getline(inputStream, exp); + return getExpressionString(exp); +} + +static std::string getExpressionString(std::string& unparsedString) { + trimString(unparsedString); + if (*(unparsedString.end() - 1) != ';') { throw VCell::Exception(std::format("Expression `{}` is not terminated with a ';' character!", unparsedString)); } + return unparsedString; +} + +/**---------------------------------------------------- +Input format: + STARTING_TIME 0.0 + ENDING_TIME 0.1 + RELATIVE_TOLERANCE 1.0E-9 + ABSOLUTE_TOLERANCE 1.0E-9 + MAX_TIME_STEP 1.0 + KEEP_EVERY 1 + DISCONTINUITIES 1 + D_B0 (t > 0.0432); (-0.0432 + t); + NUM_EQUATIONS 2 + ODE x_i INIT 0.0; + RATE ((20.0 * x_o * D_B0) - (50.0 * x_i)); + ODE x_o INIT 0.0; + RATE ( - ((20.0 * x_o * D_B0) - (50.0 * x_i)) + (1505000.0 * (3.322259136212625E-4 - (3.322259136212625E-4 * x_o) - (3.322259136212625E-4 * x_i))) - (100.0 * x_o)); +--------------------------------------------------------------*/ + +static void collectCVodeEquations(std::ifstream& inputFileStream, VCellSolverInputBreakdown& inputBreakdown) { + std::size_t numOfEquations; + inputFileStream >> numOfEquations; + std::string exp; + try { + std::string keyword; + for (std::size_t i = 0; i < numOfEquations; i++) { + // ODE + std::string varName; + inputFileStream >> keyword >> varName; + inputBreakdown.modelSettings.VARIABLE_NAMES.push_back(varName); + + // INIT + inputFileStream >> keyword; + try { + std::string expression{getExpressionString(inputFileStream)}; + inputBreakdown.modelSettings.INITIAL_CONDITION_EXPRESSIONS.push_back(expression); + } catch (VCell::Exception& ex) { + const std::string errMsg{"Initial condition expression for [" + inputBreakdown.modelSettings.VARIABLE_NAMES.back() + "] " + ex.getMessage()}; + throw VCell::Exception(errMsg); + } + + // RATE + inputFileStream >> keyword; + try { + std::string expression{getExpressionString(inputFileStream)}; + inputBreakdown.modelSettings.RATE_EXPRESSIONS.push_back(expression); + } catch (VCell::Exception& ex) { + const std::string errMsg{"Rate expression for [" + inputBreakdown.modelSettings.VARIABLE_NAMES.back() + "] " + ex.getMessage()}; + throw VCell::Exception(errMsg); + } + } + inputBreakdown.modelSettings.NUM_DIFFERENTIAL = inputBreakdown.modelSettings.RATE_EXPRESSIONS.size(); + inputBreakdown.modelSettings.NUM_ALGEBRAIC = 0; + + } catch (char* ex) { + throw VCell::Exception(std::string("VCellSolverFactory::collectCVodeEquations() : ") + ex); + } catch (VCell::Exception& ex) { + throw VCell::Exception(std::string("VCellSolverFactory::collectCVodeEquations() : ") + ex.getMessage()); + } +} + +/**---------------------------------------------------- +Input format: + STARTING_TIME 0.0 + ENDING_TIME 0.1 + RELATIVE_TOLERANCE 1.0E-9 + ABSOLUTE_TOLERANCE 1.0E-9 + MAX_TIME_STEP 1.0 + KEEP_EVERY 1 + DISCONTINUITIES 1 + D_B0 (t > 0.0432); (-0.0432 + t); + NUM_EQUATIONS 2 + VAR x_i INIT (0.0); + VAR x_o INIT (0.8); + TRANSFORM + 3.322259136212625E-4 0.0 + 0.0 1.0 + INVERSETRANSFORM + 3010.0 0.0 + 0.0 1.0 + RHS DIFFERENTIAL 1 ALGEBRAIC 1 + (3.322259136212625E-4 * ((20.0 * x_o * D_B0) - (50.0 * x_i))); + ((1505000.0 * (3.3222591362126253E-4 - (3.322259136212625E-4 * x_i) - (3.322259136212625E-4 * x_o))) - (100.0 * x_o)); +--------------------------------------------------------------*/ +static void collectIDAEquations(std::ifstream& inputFileStream, VCellSolverInputBreakdown& inputBreakdown) { + std::size_t numOfEquations; + inputFileStream >> numOfEquations; + try { + std::string keyword; + std::string _ignored; + + // There should be number of: + // * VAR statements + // * TRANSFORM entries + // * INVERSETRANSFORM entries + // * RHS entries + // We will remove sections when we've gotten the necessary amount of each + std::unordered_map sections = {{"VAR", 0}, {"TRANSFORM", 0} , {"INVERSETRANSFORM", 0}, {"RHS", 0} }; + + while (!sections.empty()) { + inputFileStream >> keyword; + if (!sections.contains(keyword)) throw VCell::Exception(std::string("Encountered unexpected keyword `") + keyword + "`"); + + if (keyword == "VAR") { + std::string varName, initialConditionExpression; + inputFileStream >> varName; + inputBreakdown.modelSettings.VARIABLE_NAMES.push_back(std::move(varName)); + inputFileStream >> keyword; // INIT keyword + try { + inputBreakdown.modelSettings.INITIAL_CONDITION_EXPRESSIONS.push_back(getExpressionString(inputFileStream)); + } catch (VCell::Exception& ex) { + throw VCell::Exception(std::string("Initial condition expression for [") + inputBreakdown.modelSettings.VARIABLE_NAMES.back() + "] " + ex.getMessage()); + } + // check for completion + if (sections[keyword] == numOfEquations - 1) sections.erase(keyword); + else sections[keyword]++; + } else if (keyword == "TRANSFORM") { + std::getline(inputFileStream, _ignored); // go to next line + for (std::size_t i = 0; i < numOfEquations * numOfEquations; i++) { + std::string nextTerm; + inputFileStream >> nextTerm; + inputBreakdown.modelSettings.FLAT_TRANSFORM_MATRIX.push_back(nextTerm); + } + // Affirm completion + sections.erase(keyword); + } else if (keyword == "INVERSETRANSFORM") { + std::getline(inputFileStream, _ignored); // go to next line + for (std::size_t i = 0; i < numOfEquations * numOfEquations; i++) { + std::string nextTerm; + inputFileStream >> nextTerm; + inputBreakdown.modelSettings.FLAT_INVERSE_TRANSFORM_MATRIX.push_back(nextTerm); + } + // Affirm completion + sections.erase(keyword); + } else if (keyword == "RHS") { + std::size_t numDifferentials, numAlgebraics; + inputFileStream >> keyword >> numDifferentials; + if (keyword != "DIFFERENTIAL") throw VCell::Exception("Error: Expected keyword `DIFFERENTIAL`, found `" + keyword +"`"); + inputBreakdown.modelSettings.NUM_DIFFERENTIAL = numDifferentials; + inputFileStream >> keyword >> numAlgebraics; + if (keyword != "ALGEBRAIC") throw VCell::Exception("Error: Expected keyword `ALGEBRAIC`, found `" + keyword +"`"); + inputBreakdown.modelSettings.NUM_ALGEBRAIC = numAlgebraics; + if (numDifferentials + numAlgebraics != numOfEquations) { + throw VCell::Exception(std::format("The sum of differential ({}) and algebraic ({}) equations does not equal the total number of equations ({})", numDifferentials, numAlgebraics, numOfEquations)); + } + std::getline(inputFileStream, _ignored); // go to next line + for (std::size_t i = 0; i < numOfEquations; i ++) { + try { + inputBreakdown.modelSettings.RATE_EXPRESSIONS.push_back(getExpressionString(inputFileStream)); + } catch (VCell::Exception& ex) { + throw VCell::Exception(std::format("RHS[{}] {}", i, ex.getMessage())); + } + } + // Affirm completion + sections.erase(keyword); + } else { + std::string errMsg{"Fatal error: inconsistent state - `" + keyword + "` is a valid section, but no logic exists to deal with it!"}; + throw VCell::Exception(errMsg); + } + } + } catch (const char* ex) { + throw VCell::Exception(std::string("VCellSolverFactory::collectIDAEquations() : ") + ex); + } catch (VCell::Exception& ex) { + throw VCell::Exception(std::string("VCellSolverFactory::collectIDAEquations() : ") + ex.getMessage()); + } catch (...) { + throw VCell::Exception("VCellSolverFactory::collectIDAEquations() caught unknown exception"); + } +} + +/**---------------------------------------------------- +Input format: + ... (STEADY_STATE TRUE) + RESOLUTION 0.0000000001 + MAX_NEWTON_ITERATIONS + MAXIMUM_FORWARD_DURATION + DERIVATION_FACTOR 0.001 + STOP_CRITERIA DISTANCE_AND_RATE + ... +--------------------------------------------------------------*/ +static void collectSteadyStateTerms(std::ifstream& inputFileStream, VCellSolverInputBreakdown& inputBreakdown) { + std::unordered_set sections = {"RESOLUTION", "MAX_NEWTON_ITERATIONS", + "MAXIMUM_FORWARD_DURATION", "DERIVATION_FACTOR", "STOP_CRITERIA"}; + + do { + std::string keyword; + inputFileStream >> keyword; + if (keyword == "RESOLUTION") { inputFileStream >> inputBreakdown.steadyStateSettings.RESOLUTION; } + else if (keyword == "MAXIMUM_NEWTON_ITERATIONS") { inputFileStream >> inputBreakdown.steadyStateSettings.MAX_ITERATIONS; } + else if (keyword == "MAXIMUM_FORWARD_DURATION") { inputFileStream >> inputBreakdown.steadyStateSettings.MAX_FORWARD_DURATION; } + else if (keyword == "DERIVATION_FACTOR") { inputFileStream >> inputBreakdown.steadyStateSettings.DERIVATION_FACTOR; } + else if (keyword == "STOP_CRITERIA") { + std::string stopCriteria; + inputFileStream >> stopCriteria; + if (stopCriteria == "DISTANCE_ONLY") { inputBreakdown.steadyStateSettings.STOP_CRITERIA = VCellSteadyStateStopCriteria::DISTANCE_ONLY; } + else if (stopCriteria == "RATE_ONLY") { inputBreakdown.steadyStateSettings.STOP_CRITERIA = VCellSteadyStateStopCriteria::RATE_ONLY; } + else if (stopCriteria == "DISTANCE_AND_RATE") { inputBreakdown.steadyStateSettings.STOP_CRITERIA = VCellSteadyStateStopCriteria::DISTANCE_AND_RATE; } + else if (stopCriteria == "DISTANCE_OR_RATE") { inputBreakdown.steadyStateSettings.STOP_CRITERIA = VCellSteadyStateStopCriteria::DISTANCE_OR_RATE; } + else throw VCell::Exception("Unknown stop criteria `" + keyword + "`"); + } else throw VCell::Exception("Unknown steady state keyword `" + keyword + "`"); + if (!sections.contains(keyword)) throw VCell::Exception("Duplicate section `" + keyword + "` detected."); + sections.erase(keyword); + } while (!sections.empty()); +} + +static void loadJMSInfo(std::istream &ifsInput, int taskID) { + #ifndef USE_MESSAGING + return; // Only useful for messaging; let's not waste time! + #else + + if (taskID < 0) { + SimulationMessaging::create(); + return; // No need to do any parsing + } + std::string broker; + std::string smqUserName; + std::string password; + std::string qName; + std::string topicName; + std::string vCellUsername; + int simKey, jobIndex; + + while (!ifsInput.eof()) { + std::string nextToken; + ifsInput >> nextToken; + if (nextToken.empty()) continue; + if (nextToken[0] == '#') { + // getline(ifsInput, nextToken); // Is this ignoring because of a comment? + ifsInput.ignore('\n'); + continue; + } + if (nextToken == "JMS_PARAM_END") { ifsInput.ignore(EOF); } else if ( + nextToken == "JMS_BROKER") { ifsInput >> broker; } else if ( + nextToken == "JMS_USER") { ifsInput >> smqUserName >> password; } else if ( + nextToken == "JMS_QUEUE") { ifsInput >> qName; } else if ( + nextToken == "JMS_TOPIC") { ifsInput >> topicName; } else if (nextToken == "VCELL_USER") { + ifsInput >> vCellUsername; + } else if (nextToken == "SIMULATION_KEY") { + ifsInput >> simKey; + continue; + } else if (nextToken == "JOB_INDEX") { + ifsInput >> jobIndex; + continue; + } + } + + SimulationMessaging::create(broker.c_str(), smqUserName.c_str(), + password.c_str(), qName.c_str(), topicName.c_str(), + vCellUsername.c_str(), simKey, jobIndex, taskID); + #endif +} diff --git a/IDAWin/VCellSolverFactory.h b/IDAWin/VCellSolverFactory.h new file mode 100644 index 000000000..9e81b35df --- /dev/null +++ b/IDAWin/VCellSolverFactory.h @@ -0,0 +1,26 @@ +// +// Created by Logan Drescher on 6/26/25. +// + +#ifndef VCELLSOLVERFACTORY_H +#define VCELLSOLVERFACTORY_H +#include +#include +#include "VCellSolver.h" +#include "VCellSolverInput.h" + + + + +class VCellSolverFactory { + public: + static VCellSolver* produceVCellSolver(std::ifstream& inputFileStream, int taskID); + static VCellSolverInputBreakdown parseInputFile(std::ifstream& inputFileStream, int taskID); + private: + static VCellSolverTypes determineSolverType(const std::string& solverName); + static void processEquations(std::ifstream& inputFileStream, VCellSolverInputBreakdown& inputBreakdown); +}; + + + +#endif //VCELLSOLVERFACTORY_H diff --git a/IDAWin/VCellSolverInput.cpp b/IDAWin/VCellSolverInput.cpp new file mode 100644 index 000000000..8f0f7bf26 --- /dev/null +++ b/IDAWin/VCellSolverInput.cpp @@ -0,0 +1,7 @@ +// +// Created by Logan Drescher on 6/26/25. +// + +#include "VCellSolverInput.h" + +// See "TODO" in header file diff --git a/IDAWin/VCellSolverInput.h b/IDAWin/VCellSolverInput.h new file mode 100644 index 000000000..08b9ca4d1 --- /dev/null +++ b/IDAWin/VCellSolverInput.h @@ -0,0 +1,81 @@ +// +// Created by Logan Drescher on 6/26/25. +// + +#ifndef VCELLSOLVERINPUT_H +#define VCELLSOLVERINPUT_H +#include +#include +#include +#include "Expression.h" + +enum VCellSolverTypes{ CVODE, IDA, STEADY_STATE}; +enum VCellSteadyStateStopCriteria{DISTANCE_ONLY, RATE_ONLY, DISTANCE_OR_RATE, DISTANCE_AND_RATE}; + +/// TODO: For better memory management, vectors should have their size initialized by the +/// values in the input file, and allocated on the heap! +struct VCellSolverInputBreakdown { + // Typedefs + + struct ModelSettings { + std::vector PARAMETER_NAMES; + std::vector VARIABLE_NAMES; + std::vector INITIAL_CONDITION_EXPRESSIONS; + std::vector RATE_EXPRESSIONS; + std::vector FLAT_TRANSFORM_MATRIX; + std::vector FLAT_INVERSE_TRANSFORM_MATRIX; + int NUM_DIFFERENTIAL; + int NUM_ALGEBRAIC; + }; + + struct TimeCourseSettings { + realtype STARTING_TIME; + realtype ENDING_TIME; + realtype RELATIVE_TOLERANCE; + realtype ABSOLUTE_TOLERANCE; + double OUTPUT_TIME_STEP = 0.0; + double MAX_TIME_STEP = 0.0; + std::vector TIME_POINTS; + long KEEP_EVERY = 0.0l; + }; + + struct SteadyStateSettings { + realtype RESOLUTION; + realtype DERIVATION_FACTOR; // how far to newton step to satisfy df/dt + realtype MAX_ITERATIONS; // how many times to newton-step before giving up + realtype MAX_FORWARD_DURATION; + VCellSteadyStateStopCriteria STOP_CRITERIA; + }; + + struct DiscontinuityComponents { + std::string symbol; + VCell::Expression* discontinuityExpression; + VCell::Expression* rootFindingExpression; + }; + + + struct DiscontinuitiesSettings { + std::vector DISCONTINUITIES; + }; + + struct EventComponents { + std::string eventName; + std::string triggerExpression; + std::string delayDurationExpression; + std::vector> eventAssignments; + bool shouldUseValuesAtTriggerTime = true; + }; + + struct EventSettings { + std::vector EVENTS; + }; + + VCellSolverTypes solverType; + ModelSettings modelSettings; + TimeCourseSettings timeCourseSettings; + SteadyStateSettings steadyStateSettings; + DiscontinuitiesSettings discontinuitiesSettings; + EventSettings eventSettings; +}; + +#endif //VCELLSOLVERINPUT_H diff --git a/IDAWin/VCellSundialsSolver.cpp b/IDAWin/VCellSundialsSolver.cpp index 06257a695..99286c686 100644 --- a/IDAWin/VCellSundialsSolver.cpp +++ b/IDAWin/VCellSundialsSolver.cpp @@ -1,4 +1,4 @@ -#include +#include "SimpleSymbolTable.h" #include "StoppedByUserException.h" #include "VCellSundialsSolver.h" #include "OdeResultSet.h" @@ -8,33 +8,21 @@ #include #include - -#include - #ifdef USE_MESSAGING #include #endif -static void trimString(std::string &str) { - std::string::size_type pos = str.find_last_not_of(" \r\n"); - if (pos != std::string::npos) { - str.erase(pos + 1); - pos = str.find_first_not_of(" \r\n"); - if (pos != std::string::npos) { str.erase(0, pos); } - } else { str.erase(str.begin(), str.end()); } -} - -VCell::Expression *VCellSundialsSolver::readExpression(std::istream &inputstream) { - std::string exp; - getline(inputstream, exp); - trimString(exp); - if (*(exp.end() - 1) != ';') { throw VCell::Exception(BAD_EXPRESSION_MSG); } - return new VCell::Expression(exp); -} +/** * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Local Helper Function Prototypes + * * * * * * * * * * * * * * * * * * * * * * * * * * * **/ +static void trimString(std::string &str); +/** * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Instance Methods + * * * * * * * * * * * * * * * * * * * * * * * * * * * **/ VCellSundialsSolver::VCellSundialsSolver() { - NEQ = 0; - NPARAM = 0; + NUM_EQUATIONS = 0; + NUM_PARAMETERS = 0; STARTING_TIME = 0.0; ENDING_TIME = 0.0; RelativeTolerance = 0.0; @@ -70,7 +58,7 @@ VCellSundialsSolver::VCellSundialsSolver() { VCellSundialsSolver::~VCellSundialsSolver() { N_VDestroy_Serial(y); - for (int i = 0; i < NEQ; i++) { delete initialConditionExpressions[i]; } + for (int i = 0; i < NUM_EQUATIONS; i++) { delete initialConditionExpressions[i]; } delete[] initialConditionExpressions; delete[] variableNames; delete[] paramNames; @@ -99,21 +87,21 @@ VCellSundialsSolver::~VCellSundialsSolver() { eventExeList.clear(); } -void VCellSundialsSolver::updateTempRowData(double currTime) { - tempRowData[0] = currTime; - for (int i = 0; i < NEQ; i++) { tempRowData[i + 1] = NV_Ith_S(y, i); } -} - void VCellSundialsSolver::writeData(double currTime, FILE *outputFile) { updateTempRowData(currTime); odeResultSet->addRow(tempRowData); writeFileData(outputFile); } +void VCellSundialsSolver::updateTempRowData(double currTime) { + tempRowData[0] = currTime; + for (int i = 0; i < NUM_EQUATIONS; i++) { tempRowData[i + 1] = NV_Ith_S(y, i); } +} + void VCellSundialsSolver::writeFileData(FILE *outputFile) { if (outputFile != nullptr) { fprintf(outputFile, "%0.17E", tempRowData[0]); - for (int i = 1; i < NEQ + 1; i++) { fprintf(outputFile, "\t%0.17E", tempRowData[i]); } + for (int i = 1; i < NUM_EQUATIONS + 1; i++) { fprintf(outputFile, "\t%0.17E", tempRowData[i]); } fprintf(outputFile, "\n"); } } @@ -170,56 +158,121 @@ void VCellSundialsSolver::printProgress(double currTime, double &lastPercentile, } } -void VCellSundialsSolver::readInput(std::istream &inputstream) { +// void VCellSundialsSolver::readInput(std::istream &inputFileStream) { +// try { +// if (solver != nullptr) { throw "readInput should only be called once"; } +// while (!inputFileStream.eof()) { +// std::string name; +// inputFileStream >> name; +// if (name.empty()) { continue; } +// if (name == "SOLVER") { +// inputFileStream >> name; +// if (name != getSolverName()) { throw "Wrong solver"; } +// } else if (name == "STARTING_TIME") { inputFileStream >> STARTING_TIME; } else if ( +// name == "ENDING_TIME") { inputFileStream >> ENDING_TIME; } else if ( +// name == "RELATIVE_TOLERANCE") { inputFileStream >> RelativeTolerance; } else if ( +// name == "ABSOLUTE_TOLERANCE") { inputFileStream >> AbsoluteTolerance; } else if ( +// name == "MAX_TIME_STEP") { inputFileStream >> maxTimeStep; } else if (name == "KEEP_EVERY") { +// inputFileStream >> keepEvery; +// } else if (name == "OUTPUT_TIME_STEP") { +// double outputTimeStep = 0.0; +// inputFileStream >> outputTimeStep; +// double timePoint = 0.0; +// int count = 1; +// while (STARTING_TIME + count * outputTimeStep < ENDING_TIME + 1E-12 * ENDING_TIME) { +// timePoint = STARTING_TIME + count * outputTimeStep; +// outputTimes.push_back(timePoint); +// count++; +// } +// ENDING_TIME = outputTimes[outputTimes.size() - 1]; +// } else if (name == "OUTPUT_TIMES") { +// int totalNumTimePoints; +// double timePoint; +// inputFileStream >> totalNumTimePoints; +// for (int i = 0; i < totalNumTimePoints; i++) { +// inputFileStream >> timePoint; +// if (timePoint > STARTING_TIME && timePoint <= ENDING_TIME) { outputTimes.push_back(timePoint); } +// } +// if (outputTimes[outputTimes.size() - 1] < ENDING_TIME) { outputTimes.push_back(ENDING_TIME); } +// } else if (name == "DISCONTINUITIES") { readDiscontinuities(inputFileStream); } +// else if (name == "NUM_PARAMETERS") { +// inputFileStream >> NUM_PARAMETERS; +// paramNames = new std::string[NUM_PARAMETERS]; +// for (int i = 0; i < NUM_PARAMETERS; i++) { inputFileStream >> paramNames[i]; } +// } else if (name == "NUM_EQUATIONS") { +// inputFileStream >> NUM_EQUATIONS; +// variableNames = new std::string[NUM_EQUATIONS]; +// initialConditionExpressions = new VCell::Expression *[NUM_EQUATIONS]; +// readEquations(inputFileStream); +// } else if (name == "EVENTS") { readEvents(inputFileStream); } else { +// std::string msg = "Unexpected token \"" + name + "\" in the input file!"; +// throw VCell::Exception(msg); +// } +// } +// initialize(); +// } catch (char *ex) { throw VCell::Exception(std::string("VCellSundialsSolver::readInput() : ") + ex); } catch ( +// VCell::Exception &ex) { +// throw VCell::Exception(std::string("VCellSundialsSolver::readInput() : ") + ex.getMessage()); +// } +// } + +void VCellSundialsSolver::configureFromInput(VCellSolverInputBreakdown& inputBreakdown) { try { - if (solver != 0) { throw "readInput should only be called once"; } - std::string name; - while (!inputstream.eof()) { - name = ""; - inputstream >> name; - if (name.empty()) { continue; } - if (name == "SOLVER") { - inputstream >> name; - if (name != getSolverName()) { throw "Wrong solver"; } - } else if (name == "STARTING_TIME") { inputstream >> STARTING_TIME; } else if ( - name == "ENDING_TIME") { inputstream >> ENDING_TIME; } else if ( - name == "RELATIVE_TOLERANCE") { inputstream >> RelativeTolerance; } else if ( - name == "ABSOLUTE_TOLERANCE") { inputstream >> AbsoluteTolerance; } else if ( - name == "MAX_TIME_STEP") { inputstream >> maxTimeStep; } else if (name == "KEEP_EVERY") { - inputstream >> keepEvery; - } else if (name == "OUTPUT_TIME_STEP") { - double outputTimeStep = 0.0; - inputstream >> outputTimeStep; - double timePoint = 0.0; - int count = 1; - while (STARTING_TIME + count * outputTimeStep < ENDING_TIME + 1E-12 * ENDING_TIME) { - timePoint = STARTING_TIME + count * outputTimeStep; - outputTimes.push_back(timePoint); - count++; - } - ENDING_TIME = outputTimes[outputTimes.size() - 1]; - } else if (name == "OUTPUT_TIMES") { - int totalNumTimePoints; - double timePoint; - inputstream >> totalNumTimePoints; - for (int i = 0; i < totalNumTimePoints; i++) { - inputstream >> timePoint; - if (timePoint > STARTING_TIME && timePoint <= ENDING_TIME) { outputTimes.push_back(timePoint); } - } - if (outputTimes[outputTimes.size() - 1] < ENDING_TIME) { outputTimes.push_back(ENDING_TIME); } - } else if (name == "DISCONTINUITIES") { readDiscontinuities(inputstream); } else if ( - name == "NUM_PARAMETERS") { - inputstream >> NPARAM; - paramNames = new std::string[NPARAM]; - for (int i = 0; i < NPARAM; i++) { inputstream >> paramNames[i]; } - } else if (name == "NUM_EQUATIONS") { - inputstream >> NEQ; - variableNames = new std::string[NEQ]; - initialConditionExpressions = new VCell::Expression *[NEQ]; - readEquations(inputstream); - } else if (name == "EVENTS") { readEvents(inputstream); } else { - std::string msg = "Unexpected token \"" + name + "\" in the input file!"; - throw VCell::Exception(msg); + if (solver != nullptr) { throw "readInput should only be called once"; } + if (inputBreakdown.solverType != getSolverType()) throw VCell::Exception("Fatal Error: Solver mismatch detected!"); + STARTING_TIME = inputBreakdown.timeCourseSettings.STARTING_TIME; + ENDING_TIME = inputBreakdown.timeCourseSettings.ENDING_TIME; + RelativeTolerance = inputBreakdown.timeCourseSettings.RELATIVE_TOLERANCE; + AbsoluteTolerance = inputBreakdown.timeCourseSettings.ABSOLUTE_TOLERANCE; + maxTimeStep = inputBreakdown.timeCourseSettings.MAX_TIME_STEP; + keepEvery = inputBreakdown.timeCourseSettings.KEEP_EVERY; + ///TODO: replace "1e-12" with "SMALL_REAL" constant? Probably need to change outputTimes to `std::vector` + if (inputBreakdown.timeCourseSettings.OUTPUT_TIME_STEP != 0.0){ + for (int count = 1; STARTING_TIME + count * inputBreakdown.timeCourseSettings.OUTPUT_TIME_STEP < ENDING_TIME + 1E-12 * ENDING_TIME; count++) + outputTimes.push_back(STARTING_TIME + count * inputBreakdown.timeCourseSettings.OUTPUT_TIME_STEP); + ENDING_TIME = outputTimes.back(); + } else if (!inputBreakdown.timeCourseSettings.TIME_POINTS.empty()) { + for (auto elem : inputBreakdown.timeCourseSettings.TIME_POINTS) outputTimes.push_back(elem); + } + numDiscontinuities = inputBreakdown.discontinuitiesSettings.DISCONTINUITIES.size(); + odeDiscontinuities = new OdeDiscontinuity*[numDiscontinuities]; + for (int i = 0; i < numDiscontinuities; i++) { + odeDiscontinuities[i] = new OdeDiscontinuity(); + odeDiscontinuities[i]->discontinuitySymbol = inputBreakdown.discontinuitiesSettings.DISCONTINUITIES[i].symbol; + odeDiscontinuities[i]->discontinuityExpression = inputBreakdown.discontinuitiesSettings.DISCONTINUITIES[i].discontinuityExpression; + odeDiscontinuities[i]->rootFindingExpression = inputBreakdown.discontinuitiesSettings.DISCONTINUITIES[i].rootFindingExpression; + } + NUM_PARAMETERS = inputBreakdown.modelSettings.PARAMETER_NAMES.size(); + paramNames = new std::string[NUM_PARAMETERS]; + for (int i = 0; i < NUM_PARAMETERS; i++) {paramNames[i] = inputBreakdown.modelSettings.PARAMETER_NAMES[i];} + NUM_EQUATIONS = inputBreakdown.modelSettings.VARIABLE_NAMES.size(); + variableNames = new std::string[NUM_EQUATIONS]; + initialConditionExpressions = new VCell::Expression*[NUM_EQUATIONS]; + readEquations(inputBreakdown); + numEvents = inputBreakdown.eventSettings.EVENTS.size(); + events = new Event*[numEvents]; + for (int i = 0; i < inputBreakdown.eventSettings.EVENTS.size(); i++) { + events[i] = new Event(); + events[i]->name = inputBreakdown.eventSettings.EVENTS[i].eventName; + events[i]->bUseValuesAtTriggerTime = inputBreakdown.eventSettings.EVENTS[i].shouldUseValuesAtTriggerTime; + try { + events[i]->triggerExpression = new VCell::Expression(inputBreakdown.eventSettings.EVENTS[i].triggerExpression); + } catch (VCell::Exception &ex) { + throw VCell::Exception(std::string("trigger expression") + " " + ex.getMessage()); + } + + try { + if (!inputBreakdown.eventSettings.EVENTS[i].delayDurationExpression.empty()) + events[i]->delayDurationExpression = new VCell::Expression(inputBreakdown.eventSettings.EVENTS[i].delayDurationExpression); + } catch (VCell::Exception &ex) { + throw VCell::Exception(std::string("delay duration expression") + " " + ex.getMessage()); + } + + for (auto [varIndex, assignmentExpression]: inputBreakdown.eventSettings.EVENTS[i].eventAssignments) { + EventAssignment* eventAssignment = new EventAssignment(); + eventAssignment->varIndex = varIndex; + eventAssignment->assignmentExpression = new VCell::Expression(assignmentExpression); + events[i]->eventAssignmentsVec->emplace_back(eventAssignment); } } initialize(); @@ -229,59 +282,58 @@ void VCellSundialsSolver::readInput(std::istream &inputstream) { } } -void VCellSundialsSolver::readEvents(std::istream &inputstream) { - std::string token; - - inputstream >> numEvents; - events = new Event *[numEvents]; - for (int i = 0; i < numEvents; i++) { - events[i] = new Event(); - while (true) { // Break on "EVENTASSIGNMENTS" - inputstream >> token; - if (token == "EVENT") { inputstream >> events[i]->name; } else if (token == "TRIGGER") { - try { events[i]->triggerExpression = readExpression(inputstream); } catch (VCell::Exception &ex) { - throw VCell::Exception(std::string("trigger expression") + " " + ex.getMessage()); - } - } else if (token == "DELAY") { - inputstream >> token; - events[i]->bUseValuesAtTriggerTime = token == "true"; - try { events[i]->delayDurationExpression = readExpression(inputstream); } catch (VCell::Exception &ex) { - throw VCell::Exception(std::string("delay duration expression") + " " + ex.getMessage()); - } - } else if (token == "EVENTASSIGNMENTS") { - int numEventAssignments; - inputstream >> numEventAssignments; - for (int j = 0; j < numEventAssignments; j++) { - EventAssignment *ea = new EventAssignment(); - inputstream >> ea->varIndex; - try { ea->assignmentExpression = readExpression(inputstream); } catch (VCell::Exception &ex) { - throw VCell::Exception(std::string("event assignment expression") + " " + ex.getMessage()); - } - events[i]->eventAssignmentsVec->push_back(ea); - } - break; - } else { throw VCell::Exception("Unexpected token \"" + token + "\" in the input file!"); } - } - } -} +// void VCellSundialsSolver::readEvents(std::istream &inputstream) { +// std::string token; +// +// inputstream >> numEvents; +// events = new Event *[numEvents]; +// for (int i = 0; i < numEvents; i++) { +// events[i] = new Event(); +// +// while (true) { // Break on "EVENTASSIGNMENTS" +// inputstream >> token; +// if (token == "EVENT") { inputstream >> events[i]->name; } else if (token == "TRIGGER") { +// try { events[i]->triggerExpression = readExpression(inputstream); } catch (VCell::Exception &ex) { +// throw VCell::Exception(std::string("trigger expression") + " " + ex.getMessage()); +// } +// } else if (token == "DELAY") { +// inputstream >> token; +// events[i]->bUseValuesAtTriggerTime = token == "true"; +// try { events[i]->delayDurationExpression = readExpression(inputstream); } catch (VCell::Exception &ex) { +// throw VCell::Exception(std::string("delay duration expression") + " " + ex.getMessage()); +// } +// } else if (token == "EVENTASSIGNMENTS") { +// int numEventAssignments; +// inputstream >> numEventAssignments; +// for (int j = 0; j < numEventAssignments; j++) { +// EventAssignment *ea = new EventAssignment(); +// inputstream >> ea->varIndex; +// try { ea->assignmentExpression = readExpression(inputstream); } catch (VCell::Exception &ex) { +// throw VCell::Exception(std::string("event assignment expression") + " " + ex.getMessage()); +// } +// events[i]->eventAssignmentsVec->push_back(ea); +// } +// break; +// } else { throw VCell::Exception("Unexpected token \"" + token + "\" in the input file!"); } +// } +// } +// } void VCellSundialsSolver::readDiscontinuities(std::istream &inputstream) { inputstream >> numDiscontinuities; odeDiscontinuities = new OdeDiscontinuity *[numDiscontinuities]; - std::string line; - std::string exp; for (int i = 0; i < numDiscontinuities; i++) { OdeDiscontinuity *od = new OdeDiscontinuity; inputstream >> od->discontinuitySymbol; - line = ""; + std::string line = ""; getline(inputstream, line); std::string::size_type pos = line.find(";"); if (pos == std::string::npos) { std::string msg = std::string("discontinuity expression ") + BAD_EXPRESSION_MSG; throw VCell::Exception(msg); } - exp = line.substr(0, pos + 1); + std::string exp = line.substr(0, pos + 1); trimString(exp); od->discontinuityExpression = new VCell::Expression(exp); @@ -299,35 +351,35 @@ void VCellSundialsSolver::readDiscontinuities(std::istream &inputstream) { void VCellSundialsSolver::initialize() { // add parameters to symbol table - numAllSymbols = 1 + NEQ + NPARAM + numDiscontinuities; + numAllSymbols = 1 + NUM_EQUATIONS + NUM_PARAMETERS + numDiscontinuities; allSymbols = new std::string[numAllSymbols]; // t, variables, parameters, discontinuities //t allSymbols[0] = "t"; odeResultSet->addColumn("t"); // variables - for (int i = 0; i < NEQ; i++) { + for (int i = 0; i < NUM_EQUATIONS; i++) { allSymbols[1 + i] = variableNames[i]; odeResultSet->addColumn(variableNames[i]); } // parameters - for (int i = 0; i < NPARAM; i++) { allSymbols[1 + NEQ + i] = paramNames[i]; } + for (int i = 0; i < NUM_PARAMETERS; i++) { allSymbols[1 + NUM_EQUATIONS + i] = paramNames[i]; } //discontinuities for (int i = 0; i < numDiscontinuities; i++) { - allSymbols[1 + NEQ + NPARAM + i] = odeDiscontinuities[i]->discontinuitySymbol; + allSymbols[1 + NUM_EQUATIONS + NUM_PARAMETERS + i] = odeDiscontinuities[i]->discontinuitySymbol; } // default symbol table has variables, parameters and discontinuities. defaultSymbolTable = new SimpleSymbolTable(allSymbols, numAllSymbols); // initial condition can only be function of parameters. - initialConditionSymbolTable = new SimpleSymbolTable(allSymbols + 1 + NEQ, NPARAM); - for (int i = 0; i < NEQ; i++) { initialConditionExpressions[i]->bindExpression(initialConditionSymbolTable); } + initialConditionSymbolTable = new SimpleSymbolTable(allSymbols + 1 + NUM_EQUATIONS, NUM_PARAMETERS); + for (int i = 0; i < NUM_EQUATIONS; i++) { initialConditionExpressions[i]->bindExpression(initialConditionSymbolTable); } if (numDiscontinuities > 0) { rootsFound = new int[2 * numDiscontinuities]; discontinuityValues = new double[numDiscontinuities]; // discontinuities can't be function of discontinuity symbols - discontinuitySymbolTable = new SimpleSymbolTable(allSymbols, 1 + NEQ + NPARAM); + discontinuitySymbolTable = new SimpleSymbolTable(allSymbols, 1 + NUM_EQUATIONS + NUM_PARAMETERS); for (int i = 0; i < numDiscontinuities; i++) { odeDiscontinuities[i]->discontinuityExpression->bindExpression(discontinuitySymbolTable); odeDiscontinuities[i]->rootFindingExpression->bindExpression(discontinuitySymbolTable); @@ -335,10 +387,10 @@ void VCellSundialsSolver::initialize() { } if (numEvents > 0) { for (int i = 0; i < numEvents; i++) { events[i]->bind(defaultSymbolTable); } } - values = new realtype[1 + NEQ + NPARAM + numDiscontinuities]; - tempRowData = new realtype[1 + NEQ]; + values = new realtype[1 + NUM_EQUATIONS + NUM_PARAMETERS + numDiscontinuities]; + tempRowData = new realtype[1 + NUM_EQUATIONS]; - y = N_VNew_Serial(NEQ); + y = N_VNew_Serial(NUM_EQUATIONS); if (y == nullptr) { throw "Out of Memory"; } } @@ -373,7 +425,7 @@ bool VCellSundialsSolver::updateDiscontinuities(realtype t, bool bOnRootReturn) std::cout << std::endl; // copy discontinuity values to values to evaluate RHS - if (bUpdated) { memcpy(values + 1 + NEQ + NPARAM, discontinuityValues, numDiscontinuities * sizeof(double)); } + if (bUpdated) { memcpy(values + 1 + NUM_EQUATIONS + NUM_PARAMETERS, discontinuityValues, numDiscontinuities * sizeof(double)); } return bUpdated; } @@ -386,7 +438,7 @@ void VCellSundialsSolver::initDiscontinuities() { discontinuityValues[i] = odeDiscontinuities[i]->discontinuityExpression->evaluateVector(values); } // copy discontinuity values to values to evaluate RHS - memcpy(values + 1 + NEQ + NPARAM, discontinuityValues, numDiscontinuities * sizeof(double)); + memcpy(values + 1 + NUM_EQUATIONS + NUM_PARAMETERS, discontinuityValues, numDiscontinuities * sizeof(double)); } void VCellSundialsSolver::checkDiscontinuityConsistency() const { @@ -447,7 +499,7 @@ void VCellSundialsSolver::printVariableValues(realtype t) { updateTandVariableValues(t, y); std::cout << "variable values are" << std::endl; std::cout << "t " << values[0] << std::endl; - for (int i = 0; i < NEQ; i++) { std::cout << variableNames[i] << " " << values[i + 1] << std::endl; } + for (int i = 0; i < NUM_EQUATIONS; i++) { std::cout << variableNames[i] << " " << values[i + 1] << std::endl; } std::cout << std::endl; } @@ -564,3 +616,27 @@ void VCellSundialsSolver::checkStopRequested(double time, long numIterations) { if (SimulationMessaging::getInstVar()->isStopRequested()) { throw StoppedByUserException("stopped by user"); } #endif } + +/** * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Static Methods + * * * * * * * * * * * * * * * * * * * * * * * * * * * **/ + +VCell::Expression *VCellSundialsSolver::readExpression(std::istream &inputstream) { + std::string exp; + getline(inputstream, exp); + trimString(exp); + if (*(exp.end() - 1) != ';') { throw VCell::Exception(BAD_EXPRESSION_MSG); } + return new VCell::Expression(exp); +} + +/** * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Local Helper Functions + * * * * * * * * * * * * * * * * * * * * * * * * * * * **/ +static void trimString(std::string &str) { + std::string::size_type pos = str.find_last_not_of(" \r\n"); + if (pos != std::string::npos) { + str.erase(pos + 1); + pos = str.find_first_not_of(" \r\n"); + if (pos != std::string::npos) { str.erase(0, pos); } + } else { str.erase(str.begin(), str.end()); } +} diff --git a/IDAWin/VCellSundialsSolver.h b/IDAWin/VCellSundialsSolver.h index b3c26f638..8b5d3b170 100644 --- a/IDAWin/VCellSundialsSolver.h +++ b/IDAWin/VCellSundialsSolver.h @@ -6,90 +6,174 @@ #include #include - #include - - #include #include -class SymbolTable; -class OdeResultSet; +#include "VCellSolver.h" + +class SymbolTable; // Defined in SymbolTable.h +class OdeResultSet; // Defined in OdeResultSet.h +struct Event; +struct EventAssignment; +struct EventExecution; +struct OdeDiscontinuity; #define bytesPerSample 25 -#define MaxFileSizeBytes 1000000000 /* 1 gigabyte */ +#define MaxFileSizeBytes 1000000000 /* 1 gigabyte */ #define BAD_EXPRESSION_MSG " is not terminated by ';'" #define MAX_NUM_EVENTS_DISCONTINUITIES_EVAL 50 +class VCellSundialsSolver : public VCellSolver { + public: + VCellSundialsSolver(); + virtual ~VCellSundialsSolver(); + + // void readInput(std::istream &inputFileStream); + + void configureFromInput(VCellSolverInputBreakdown& inputBreakdown) override; + + OdeResultSet *getResultSet() const { return this->odeResultSet; } + [[nodiscard]] int getNumEquations() const { return this->NUM_EQUATIONS; } + VCell::Expression **getInitialConditionExpressions() const { return this->initialConditionExpressions; } + void setStartingTime(const realtype newStartingTime) { this->STARTING_TIME = newStartingTime; } + void setEndingTime(const realtype newEndingTime) { this->ENDING_TIME = newEndingTime; } + //void setOutputTimes(int count, double* newOutputTimes); + SymbolTable *getSymbolTable() const { return this->defaultSymbolTable; } + + static void checkStopRequested(double, long); + + protected: + // 0 : t + // 1 ~ N : variable values + // N+1 ~ N+NPARAM : parameter values + // N+NPARAM+1 ~ N+NPARAM+numDiscontinuites : discontinuity values + realtype *values; + // 0 ~ N-1 : equations + VCell::Expression **initialConditionExpressions; + SymbolTable *initialConditionSymbolTable; + OdeResultSet *odeResultSet; // mainly for parameter optimization use, but it also stores column names + + void *solver; // the memory for solver + std::string recoverableErrMsg; + + int NUM_EQUATIONS; + int NUM_PARAMETERS; + realtype STARTING_TIME; + realtype ENDING_TIME; + realtype RelativeTolerance; + realtype AbsoluteTolerance; + long keepEvery; + double maxTimeStep; + std::vector outputTimes; + double *tempRowData; // data for current time to be written to output file and to be added to odeResultSet + + int numDiscontinuities; + OdeDiscontinuity **odeDiscontinuities; + SymbolTable *discontinuitySymbolTable; + double *discontinuityValues; + int *rootsFound; + + std::string *paramNames; + std::string *variableNames; // variables + std::string *allSymbols; + int numAllSymbols; + SymbolTable *defaultSymbolTable; + N_Vector y; + + static VCell::Expression *readExpression(std::istream &inputstream); + + void writeData(double currTime, FILE *outputFile); + virtual void updateTempRowData(double currTime); + void writeFileData(FILE *outputFile); + void writeFileHeader(FILE *outputFile); + void printProgress(double currTime, double &lastPercentile, clock_t &lastTime, double increment, FILE *outputFile) const; + void readDiscontinuities(std::istream &inputstream); + virtual void readEquations(std::istream &inputstream) = 0; + virtual void readEquations(VCellSolverInputBreakdown& inputBreakdown) = 0; + virtual void initialize(); + void initDiscontinuities(); + bool updateDiscontinuities(realtype t, bool bOnRootReturn); + void checkDiscontinuityConsistency() const; + void solveInitialDiscontinuities(double t); + virtual bool fixInitialDiscontinuities(double t) =0; + void printVariableValues(realtype t); + void printDiscontinuityValues(); + virtual void updateTandVariableValues(realtype t, N_Vector y) =0; + int RootFn(realtype t, N_Vector y, realtype *gout); + virtual std::string getSolverName() =0; + virtual VCellSolverTypes getSolverType() =0; + bool executeEvents(realtype realTimeVar); + double getNextEventTime(); + + private: + Event **events; + int numEvents; + std::list eventExeList; + + // void readEvents(std::istream &inputstream); + void testEventTriggers(realtype Time); +}; + struct EventAssignment { int varIndex; - VCell::Expression* assignmentExpression; + VCell::Expression *assignmentExpression; - ~EventAssignment() { - delete assignmentExpression; - } - void bind(SymbolTable* symbolTable) const { - assignmentExpression->bindExpression(symbolTable); + ~EventAssignment() { delete assignmentExpression; } + void bind(SymbolTable *symbolTable) const { assignmentExpression->bindExpression(symbolTable); } +}; + +struct EventExecution { + realtype exeTime; + Event *event0; + double *targetValues; + + EventExecution(Event *e) { + this->exeTime = RCONST(0.0); + this->event0 = e; + this->targetValues = nullptr; } + + ~EventExecution() { delete this->targetValues; } }; struct Event { std::string name; - VCell::Expression* triggerExpression; + VCell::Expression *triggerExpression; bool bUseValuesAtTriggerTime; - VCell::Expression* delayDurationExpression; - std::vector* eventAssignmentsVec; - bool triggerValue; + VCell::Expression *delayDurationExpression; + std::vector *eventAssignmentsVec; + bool triggerValue; Event() { this->bUseValuesAtTriggerTime = false; this->triggerExpression = nullptr; this->triggerValue = false; this->delayDurationExpression = nullptr; - this->eventAssignmentsVec = new std::vector; + this->eventAssignmentsVec = new std::vector; } + ~Event() { delete this->triggerExpression; delete this->delayDurationExpression; - for (const EventAssignment* elem : *this->eventAssignmentsVec) delete elem; + for (const EventAssignment *elem: *this->eventAssignmentsVec) delete elem; this->eventAssignmentsVec->clear(); delete this->eventAssignmentsVec; } - [[nodiscard]] bool hasDelay() const { - return this->delayDurationExpression != nullptr; - } + [[nodiscard]] bool hasDelay() const { return this->delayDurationExpression != nullptr; } - void bind(SymbolTable* symbolTable) const { + void bind(SymbolTable *symbolTable) const { this->triggerExpression->bindExpression(symbolTable); - if (this->delayDurationExpression != nullptr) { - this->delayDurationExpression->bindExpression(symbolTable); - } - for (const EventAssignment* elem : *this->eventAssignmentsVec) { - elem->bind(symbolTable); - } - } -}; - -struct EventExecution { - realtype exeTime; - Event* event0; - double* targetValues; - - EventExecution(Event* e) { - this->exeTime = RCONST(0.0); - this->event0 = e; - this->targetValues = nullptr; - } - ~EventExecution() { - delete this->targetValues; + if (this->delayDurationExpression != nullptr) { this->delayDurationExpression->bindExpression(symbolTable); } + for (const EventAssignment *elem: *this->eventAssignmentsVec) { elem->bind(symbolTable); } } }; struct OdeDiscontinuity { std::string discontinuitySymbol; - VCell::Expression* discontinuityExpression; - VCell::Expression* rootFindingExpression; + VCell::Expression *discontinuityExpression; + VCell::Expression *rootFindingExpression; ~OdeDiscontinuity() { delete this->discontinuityExpression; @@ -97,96 +181,4 @@ struct OdeDiscontinuity { } }; -class VCellSundialsSolver { -public: - VCellSundialsSolver(); - virtual ~VCellSundialsSolver(); - - void readInput(std::istream& inputstream); - virtual void solve(double* paramValues=nullptr, bool bPrintProgress=false, FILE* outputFile=nullptr, void (*checkStopRequested)(double, long)=nullptr) = 0; - OdeResultSet* getResultSet() { return this->odeResultSet; } - [[nodiscard]] int getNumEquations() const { return this->NEQ; } - VCell::Expression** getInitialConditionExpressions() { return this->initialConditionExpressions; } - void setStartingTime(realtype newStartingTime) { this->STARTING_TIME = newStartingTime; } - void setEndingTime(realtype newEndingTime) { this->ENDING_TIME = newEndingTime; } - //void setOutputTimes(int count, double* newOutputTimes); - SymbolTable* getSymbolTable() { return this->defaultSymbolTable;} - - static void checkStopRequested(double, long); - -protected: - // 0 : t - // 1 ~ N : variable values - // N+1 ~ N+NPARAM : parameter values - // N+NPARAM+1 ~ N+NPARAM+numDiscontinuites : discontinuity values - realtype* values; - // 0 ~ N-1 : equations - VCell::Expression** initialConditionExpressions; - SymbolTable* initialConditionSymbolTable; - OdeResultSet* odeResultSet; // mainly for parameter optimization use but it also stores column names - - void* solver; // the memory for solver - std::string recoverableErrMsg; - - int NEQ; - int NPARAM; - realtype STARTING_TIME; - realtype ENDING_TIME; - realtype RelativeTolerance; - realtype AbsoluteTolerance; - long keepEvery; - double maxTimeStep; - std::vector outputTimes; - double* tempRowData; // data for current time to be written to output file and to be added to odeResultSet - - int numDiscontinuities; - OdeDiscontinuity** odeDiscontinuities; - SymbolTable* discontinuitySymbolTable; - double* discontinuityValues; - int* rootsFound; - - std::string* paramNames; - std::string* variableNames; // variables - std::string* allSymbols; - int numAllSymbols; - SymbolTable* defaultSymbolTable; - - N_Vector y; - void writeData(double currTime, FILE* outputFile); - virtual void updateTempRowData(double currTime); - void writeFileData(FILE* outputFile); - void writeFileHeader(FILE* outputFile); - void printProgress(double currTime, double& lastPercentile, clock_t& lastTime, double increment, FILE* outputFile) const; - - void readDiscontinuities(std::istream& inputstream); - virtual void readEquations(std::istream& inputstream) = 0; - virtual void initialize(); - - void initDiscontinuities(); - bool updateDiscontinuities(realtype t, bool bOnRootReturn); - void checkDiscontinuityConsistency() const; - - void solveInitialDiscontinuities(double t); - virtual bool fixInitialDiscontinuities(double t)=0; - - void printVariableValues(realtype t); - void printDiscontinuityValues(); - virtual void updateTandVariableValues(realtype t, N_Vector y)=0; - - int RootFn(realtype t, N_Vector y, realtype *gout); - virtual std::string getSolverName()=0; - - static VCell::Expression* readExpression(std::istream& inputstream); - bool executeEvents(realtype realTimeVar); - double getNextEventTime(); - -private: - Event** events; - int numEvents; - - void readEvents(std::istream& inputstream); - void testEventTriggers(realtype Time); - std::list eventExeList; -}; - #endif From 096ba48d7d29d430c587a6913946a22319e4ef7e Mon Sep 17 00:00:00 2001 From: Logan Drescher Date: Mon, 30 Jun 2025 10:28:56 -0400 Subject: [PATCH 05/34] more sprintf removal --- ExpressionParser/ASTIdNode.cpp | 7 ++-- ExpressionParser/ASTPowerNode.cpp | 40 +++++++++---------- .../ExpressionParserTokenManager.cpp | 13 +++--- ExpressionParser/ParseException.cpp | 5 ++- 4 files changed, 32 insertions(+), 33 deletions(-) diff --git a/ExpressionParser/ASTIdNode.cpp b/ExpressionParser/ASTIdNode.cpp index 5dd47fd44..9bbb6a4d8 100644 --- a/ExpressionParser/ASTIdNode.cpp +++ b/ExpressionParser/ASTIdNode.cpp @@ -1,6 +1,9 @@ #include #include "ASTIdNode.h" + +#include + #include "ExpressionException.h" #include "ExpressionBindingException.h" #include "RuntimeException.h" @@ -46,9 +49,7 @@ std::string ASTIdNode::infixString(int lang, NameScope* nameScope) return cbit.util.TokenMangler.getEscapedTokenJSCL(idName); } else { */ - char chrs[20]; - sprintf(chrs, "%d\0", lang); - throw RuntimeException(std::string("Lanaguage '") + chrs + " not supported"); + throw RuntimeException(std::format("Lanaguage '{}' not supported", lang)); } } diff --git a/ExpressionParser/ASTPowerNode.cpp b/ExpressionParser/ASTPowerNode.cpp index 45811536d..0b28a2a73 100644 --- a/ExpressionParser/ASTPowerNode.cpp +++ b/ExpressionParser/ASTPowerNode.cpp @@ -1,7 +1,10 @@ #include -#include +#include #include "ASTPowerNode.h" + +#include + #include "RuntimeException.h" #include "DivideByZeroException.h" #include "FunctionDomainException.h" @@ -21,9 +24,8 @@ ASTPowerNode::~ASTPowerNode() { std::string ASTPowerNode::infixString(int lang, NameScope* nameScope) { if (jjtGetNumChildren() != 2) { - char ch[20]; - sprintf(ch, "%d\0", jjtGetNumChildren()); - throw RuntimeException("There are" + std::string(ch) + " arguments for the power operator, expecting 2"); + std::string errMsg{std::format("There are {} arguments for the power operator, expecting 2", jjtGetNumChildren())}; + throw RuntimeException(errMsg); } std::string buffer; @@ -52,9 +54,8 @@ void ASTPowerNode::getStackElements(std::vector& elements) { double ASTPowerNode::evaluate(int evalType, double* values) { if (jjtGetNumChildren() != 2) { - char chrs[1000]; - sprintf(chrs, "ASTPowerNode: wrong number of arguments for '^' (%d), expected 2\0", jjtGetNumChildren()); - throw ExpressionException(chrs); + std::string errMsg{std::format("ASTPowerNode: wrong number of arguments for '^' ({}), expected 2", jjtGetNumChildren())}; + throw ExpressionException(errMsg); } // // see if there are any constant 0.0's, if there are simplify to 0.0 @@ -84,25 +85,22 @@ double ASTPowerNode::evaluate(int evalType, double* values) { if (exponentException == NULL && baseException == NULL) { if (baseValue == 0.0 && exponentValue < 0.0) { std::string childString = infixString(LANGUAGE_DEFAULT,0); - char problem[1000]; - sprintf(problem, "u^v and u=0 and v=%lf<0", exponentValue); + std::string problem{std::format("u^v and u=0 and v={}<0", exponentValue)}; std::string errorMsg = getFunctionDomainError(problem, values, "u", baseChild, "v", exponentChild); throw DivideByZeroException(errorMsg); - } else if (baseValue < 0.0 && exponentValue != MathUtil::round(exponentValue)) { - char problem[1000]; - sprintf(problem, "u^v and u=%lf<0 and v=%lf not an integer: undefined", baseValue, exponentValue); + } + if (baseValue < 0.0 && exponentValue != MathUtil::round(exponentValue)) { + std::string problem{std::format("u^v and u={}<0 and v={} not an integer: undefined", baseValue, exponentValue)}; + std::string errorMsg = getFunctionDomainError(problem, values, "u", baseChild, "v", exponentChild); + throw FunctionDomainException(errorMsg); + } + double result = pow(baseValue, exponentValue); + if (MathUtil::double_infinity == -result || MathUtil::double_infinity == result || result != result) { + std::string problem{std::format("u^v evaluated to {}, u={}, v={}", result, baseValue, exponentValue)}; std::string errorMsg = getFunctionDomainError(problem, values, "u", baseChild, "v", exponentChild); throw FunctionDomainException(errorMsg); - } else { - double result = pow(baseValue, exponentValue); - if (MathUtil::double_infinity == -result || MathUtil::double_infinity == result || result != result) { - char problem[1000]; - sprintf(problem, "u^v evaluated to %lf, u=%lf, v=%lf", result, baseValue); - std::string errorMsg = getFunctionDomainError(problem, values, "u", baseChild, "v", exponentChild); - throw FunctionDomainException(errorMsg); - } - return result; } + return result; } else if (exponentException == 0 && exponentValue == 0.0) { return 1.0; } else if (baseException == 0 && baseValue == 1.0) { diff --git a/ExpressionParser/ExpressionParserTokenManager.cpp b/ExpressionParser/ExpressionParserTokenManager.cpp index 1484ac744..7873eaa5e 100644 --- a/ExpressionParser/ExpressionParserTokenManager.cpp +++ b/ExpressionParser/ExpressionParserTokenManager.cpp @@ -525,9 +525,7 @@ void ExpressionParserTokenManager::ReInit(SimpleCharStream* stream, int lexState void ExpressionParserTokenManager::SwitchTo(int lexState) { if (lexState >= 1 || lexState < 0) { - char ex[20]; - sprintf(ex, "%d\0", lexState); - throw RuntimeException("Error: Ignoring invalid lexical state: " + std::string(ex) + ".State unchanged."); + throw RuntimeException("Error: Ignoring invalid lexical state: " + std::to_string(lexState) + ".State unchanged."); } else curLexState = lexState; @@ -609,16 +607,17 @@ Token* ExpressionParserTokenManager::getNextToken(void) input_stream->backup(1); error_after = curPos <= 1 ? "" : input_stream->GetImage(); } - char chrs[1000]; + + std::string errMsg; if (EOFSeen) - sprintf(chrs, "Lexical error at line %d, column %d. Encountered: \0", error_line, error_column); + errMsg = std::format("Lexical error at line {}, column {}. Encountered: ", error_line, error_column); else { std::string a = Exception::add_escapes(std::string(&curChar, 1)); std::string b = Exception::add_escapes(error_after); - sprintf(chrs, "Lexical error at line %d, column %d. Encountered: \"%s\" (%d) after : \"%s\"\0", error_line, error_column, a.c_str(), curChar, b.c_str()); + errMsg = std::format("Lexical error at line {}, column {}. Encountered: \"{}\" ({}) after : \"{}\"", error_line, error_column, a.c_str(), curChar, b.c_str()); } - throw RuntimeException(chrs); + throw RuntimeException(errMsg); } EOFLoop : return 0; diff --git a/ExpressionParser/ParseException.cpp b/ExpressionParser/ParseException.cpp index 1c1a77def..9893d989d 100644 --- a/ExpressionParser/ParseException.cpp +++ b/ExpressionParser/ParseException.cpp @@ -2,6 +2,8 @@ #include "ParseException.h" +#include + std::string ParseException::eol = string("\n"); ParseException::ParseException() : Exception("ParseException", "") @@ -70,8 +72,7 @@ std::string ParseException::getExactMessage(void) retval += add_escapes(tok->image); tok = tok->next; } - char chrs[128]; - sprintf(chrs, "\" at line %d, column %d\0", currentToken->next->beginLine, currentToken->next->beginColumn); + std::string chrs{std::format("\" at line {}, column {}", currentToken->next->beginLine, currentToken->next->beginColumn)}; retval += chrs; retval += "." + eol; if (numETS == 1) { From b90b008386e77a9ba7982d5b527a0f3c7e15b55a Mon Sep 17 00:00:00 2001 From: Logan Drescher Date: Thu, 13 Nov 2025 11:10:46 -0500 Subject: [PATCH 06/34] Migrated Tests to C++, fixed message param parsing --- .github/workflows/cd.yml | 19 +- .gitignore | 2 +- CMakeLists.txt | 24 +- Dockerfile | 10 +- IDAWin/CMakeLists.txt | 18 +- IDAWin/SundialsSolverInterface.cpp | 67 +++++ IDAWin/SundialsSolverInterface.h | 11 + IDAWin/SundialsSolverStandalone.cpp | 51 +--- IDAWin/VCellSolverFactory.cpp | 92 ++----- IDAWin/VCellSundialsSolver.h | 6 +- IDAWin/hello_test.cpp | 9 - IDAWin/tests/smoke/smoke.py | 72 ------ tests/CMakeLists.txt | 43 ++++ tests/unit/hello_test.cpp | 9 + .../resources}/SimID_1489333437_0_.cvodeInput | 0 .../resources}/SimID_1489333437_0_.functions | 0 .../SimID_1489333437_0_.ida.expected | 0 .../unit/resources}/SimID_1489333437_0_.log | 0 .../SimID_1489333437_0__0.simtask.xml | 0 .../resources/SimID_1607235431_0_.cvodeInput | 231 ++++++++++++++++++ .../resources/SimID_1607235431_0_.functions | 23 ++ tests/unit/resources/SimID_1607235431_0_.log | 4 + .../SimID_1607235431_0__0.simtask.xml | 162 ++++++++++++ .../resources/SimID_256118677_0_.cvodeInput | 24 ++ .../resources/SimID_256118677_0_.functions | 6 + .../resources/SimID_256118677_0_.ida.expected | 159 ++++++++++++ tests/unit/resources/SimID_256118677_0_.log | 4 + .../resources}/simpleModel_Network_orig.vcml | 0 tests/unit/smoke_test.cpp | 112 +++++++++ 29 files changed, 908 insertions(+), 250 deletions(-) create mode 100644 IDAWin/SundialsSolverInterface.cpp create mode 100644 IDAWin/SundialsSolverInterface.h delete mode 100644 IDAWin/hello_test.cpp delete mode 100755 IDAWin/tests/smoke/smoke.py create mode 100644 tests/CMakeLists.txt create mode 100644 tests/unit/hello_test.cpp rename {IDAWin/tests/smoke => tests/unit/resources}/SimID_1489333437_0_.cvodeInput (100%) rename {IDAWin/tests/smoke => tests/unit/resources}/SimID_1489333437_0_.functions (100%) rename {IDAWin/tests/smoke => tests/unit/resources}/SimID_1489333437_0_.ida.expected (100%) rename {IDAWin/tests/smoke => tests/unit/resources}/SimID_1489333437_0_.log (100%) rename {IDAWin/tests/smoke => tests/unit/resources}/SimID_1489333437_0__0.simtask.xml (100%) create mode 100644 tests/unit/resources/SimID_1607235431_0_.cvodeInput create mode 100644 tests/unit/resources/SimID_1607235431_0_.functions create mode 100644 tests/unit/resources/SimID_1607235431_0_.log create mode 100644 tests/unit/resources/SimID_1607235431_0__0.simtask.xml create mode 100644 tests/unit/resources/SimID_256118677_0_.cvodeInput create mode 100644 tests/unit/resources/SimID_256118677_0_.functions create mode 100644 tests/unit/resources/SimID_256118677_0_.ida.expected create mode 100644 tests/unit/resources/SimID_256118677_0_.log rename {IDAWin/tests/smoke => tests/unit/resources}/simpleModel_Network_orig.vcml (100%) create mode 100644 tests/unit/smoke_test.cpp diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index 3428fe41e..855bcab39 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -9,8 +9,8 @@ jobs: strategy: fail-fast: false matrix: - platform: [windows-latest, ubuntu-latest, ubuntu-24.04-arm, macos-13, macos-latest] - #platform: [windows-latest, windows-11-arm, ubuntu-latest, ubuntu-24.04-arm, macos-13, macos-latest] + platform: [windows-latest, ubuntu-latest, ubuntu-24.04-arm, macos-15-intel, macos-latest] + #platform: [windows-latest, windows-11-arm, ubuntu-latest, ubuntu-24.04-arm, macos-15-intel, macos-latest] runs-on: ${{ matrix.platform }} @@ -51,7 +51,7 @@ jobs: cp conan-profiles/CI-CD/Windows-ARM64_profile.txt $CONAN_HOME/profiles/default - name: Install MacOS dependencies - if: matrix.platform == 'macos-13' + if: matrix.platform == 'macos-15-intel' shell: bash run: | brew install conan @@ -77,7 +77,7 @@ jobs: sudo apt upgrade -y sudo apt install -y wget wget --version - wget https://github.com/conan-io/conan/releases/download/2.17.0/conan-2.17.0-amd64.deb + wget https://github.com/conan-io/conan/releases/download/2.22.2/conan-2.22.2-amd64.deb sudo apt install -y ./conan-*.deb conan --version mkdir -p ~/.conan2/profiles/ @@ -101,7 +101,7 @@ jobs: conan install . --output-folder build --build=missing -o include_messaging=False - name: Install Dependencies through Conan on MacOS - if: matrix.platform == 'macos-13' || matrix.platform == 'macos-latest' + if: matrix.platform == 'macos-latest' || matrix.platform == 'macos-15-intel' shell: bash run: | conan install . --output-folder build --build=missing @@ -140,10 +140,9 @@ jobs: cmake --build . --config Release - name: Build Unix - if: matrix.platform == 'ubuntu-latest' || matrix.platform == 'ubuntu-24.04-arm' || matrix.platform == 'macos-13' || matrix.platform == 'macos-latest' + if: matrix.platform == 'ubuntu-latest' || matrix.platform == 'ubuntu-24.04-arm' || matrix.platform == 'macos-15-intel' || matrix.platform == 'macos-latest' run: | echo "working dir is $PWD" - cd build source conanbuild.sh cmake -B . -S .. -G "Ninja" -DCMAKE_TOOLCHAIN_FILE="conan_toolchain.cmake" -DCMAKE_BUILD_TYPE=Release @@ -157,7 +156,7 @@ jobs: ./bin/SundialsSolverStandalone_x64 || true - name: Test Unix - if: matrix.platform == 'ubuntu-latest' || matrix.platform == 'ubuntu-24.04-arm' || matrix.platform == 'macos-13' || matrix.platform == 'macos-latest' + if: matrix.platform == 'ubuntu-latest' || matrix.platform == 'ubuntu-24.04-arm' || matrix.platform == 'macos-15-intel' || matrix.platform == 'macos-latest' run: | cd build @@ -168,7 +167,7 @@ jobs: - name: fix Macos shared object paths - if: matrix.platform == 'macos-13' || matrix.platform == 'macos-latest' + if: matrix.platform == 'macos-15-intel' || matrix.platform == 'macos-latest' shell: bash run: | mkdir build/upload @@ -213,7 +212,7 @@ jobs: cd ../.. - name: Upload Intel Macos binaries - if: matrix.platform == 'macos-13' + if: matrix.platform == 'macos-15-intel' uses: actions/upload-artifact@v4 with: name: macos_x86_64.tgz diff --git a/.gitignore b/.gitignore index f57332c81..6aa1b85b4 100644 --- a/.gitignore +++ b/.gitignore @@ -23,7 +23,7 @@ NFsim_v1.11/tests/smoke/SimID_273069657_0_.gdat NFsim_v1.11/tests/smoke/SimID_273069657_0_.species -IDAWin/tests/smoke/SimID_1489333437_0_.ida +*.ida CMakeUserPresets.json conan-build diff --git a/CMakeLists.txt b/CMakeLists.txt index 751592c4c..887bcec7a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,7 +11,6 @@ if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang") endif () enable_language(CXX) enable_language(C) -#enable_language(Fortran) ############################################# # If this isn't explicitly set in cmake 3.x.x, we get warnings @@ -33,7 +32,6 @@ option(OPTION_EXTRA_CONFIG_INFO "Print useful cmake debug info while configuring # # Build 64bit binaries on Mac and target Macos 10.7 or later # -# set(CMAKE_Fortran_OSX_DEPLOYMENT_TARGET_FLAG "-mmacosx-version-min=10.7" CACHE PATH "") # set(CMAKE_OSX_DEPLOYMENT_TARGET "10.7" CACHE PATH "") # # Choose 32bit or 64bit target arch on Linux @@ -51,13 +49,11 @@ if (LINUX) set (ARCH_64bit FALSE) set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -m32") set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -m32") -# set (CMAKE_Fortran_FLAGS "${CMAKE_Fortran_FLAGS} -m32") endif() if (LINUX_64bit_BINARIES) set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -m64") set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -m64") -# set (CMAKE_Fortran_FLAGS "${CMAKE_Fortran_FLAGS} -m64") endif() endif() @@ -185,11 +181,11 @@ endif() ####################################### add_subdirectory(VCellMessaging) -message(STATUS "adding ExpressionParser") add_subdirectory(ExpressionParser) -message(STATUS "adding sundials") add_subdirectory(sundials) add_subdirectory(IDAWin) +enable_testing() +add_subdirectory(tests) set(DOWNLOAD_EXTRACT_TIMESTAMP true) cmake_policy(SET CMP0135 NEW) @@ -207,21 +203,7 @@ FetchContent_Declare( GIT_REPOSITORY https://github.com/p-ranav/argparse.git ) FetchContent_MakeAvailable(argparse) -############################################# -# GoogleTest -############################################## -FetchContent_Declare( - googletest - GIT_REPOSITORY https://github.com/google/googletest.git - GIT_TAG v1.17.0 -) -if (WINDOWS) - # For Windows: Prevent overriding the parent project's compiler/linker settings - set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) -endif() -FetchContent_MakeAvailable(googletest) -enable_testing() ############################################# # @@ -237,10 +219,8 @@ if (OPTION_EXTRA_CONFIG_INFO) include(CMakePrintHelpers) cmake_print_variables(OPTION_TARGET_MESSAGING OPTION_TARGET_DOCS) -# cmake_print_variables(CMAKE_CXX_FLAGS CMAKE_C_FLAGS CMAKE_Fortran_FLAGS) cmake_print_variables(CMAKE_CXX_FLAGS CMAKE_C_FLAGS) cmake_print_variables(CMAKE_SYSTEM_NAME WINDOWS WIN32 MINGW APPLE ARCH_64bit ARCH_32bit) -# cmake_print_variables(CMAKE_CPP_COMPILER CMAKE_C_COMPILER CMAKE_CXX_COMPILER CMAKE_Fortran_COMPILER) cmake_print_variables(CMAKE_CPP_COMPILER CMAKE_C_COMPILER CMAKE_CXX_COMPILER) endif () diff --git a/Dockerfile b/Dockerfile index f92d88759..f6476148b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,12 +1,19 @@ -FROM debian:bookworm-slim AS build +FROM debian:trixie-slim AS build SHELL ["/bin/bash", "-c"] RUN apt-get -y update && apt-get install -y apt-utils && apt-get remove --purge gcc +<<<<<<< HEAD RUN apt-get install -y -qq -o=Dpkg::Use-Pty=0 clang mold ninja-build cmake libc++-dev libc++abi-dev libcurl4-openssl-dev \ git curl wget ca-certificates python3 python3-pip +======= +RUN apt-get install -y -qq -o=Dpkg::Use-Pty=0 mold ninja-build cmake clang-18 clang++-18 clang-tools-18 libc++-18-dev libc++abi-18-dev \ + libcurl4-openssl-dev git curl wget ca-certificates python3 python3-pip +>>>>>>> 88cc3915 (Fixed bad parsing when looking for messaging params) RUN rm $(which gcc) || true RUN rm $(which g++) || true RUN rm -rf /var/lib/apt/lists/* && rm /usr/bin/ld +#RUN ln -s $(which clang-18) /usr/bin/clang +#RUN ln -s $(which clang++-18) /usr/bin/clang++ RUN ln -s $(which mold) /usr/bin/ld COPY . /vcellroot @@ -14,6 +21,7 @@ COPY . /vcellroot RUN mkdir -p /vcellroot/build/bin WORKDIR /vcellroot/build +RUN clang++ --version RUN /usr/bin/cmake .. -G Ninja -DOPTION_TARGET_MESSAGING=ON -DOPTION_TARGET_DOCS=OFF RUN ninja --verbose RUN ctest -VV diff --git a/IDAWin/CMakeLists.txt b/IDAWin/CMakeLists.txt index d9480bc26..b6d66cc54 100644 --- a/IDAWin/CMakeLists.txt +++ b/IDAWin/CMakeLists.txt @@ -1,6 +1,7 @@ project(IDAWin) -set (SRC_FILES +set (SRC_FILES + SundialsSolverInterface.cpp OdeResultSet.cpp StoppedByUserException.cpp VCellCVodeSolver.cpp @@ -20,6 +21,7 @@ set (HEADER_FILES VCellCVodeSolver.h VCellIDASolver.h SteadyStateDriver.h + SundialsSolverInterface.h ) set (EXE_SRC_FILES @@ -33,19 +35,9 @@ if (ARCH_64bit) set(EXE_FILE ${EXE_FILE}_x64) endif() +target_include_directories(IDAWin PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) add_executable(${EXE_FILE} ${EXE_SRC_FILES}) target_link_libraries(${EXE_FILE} IDAWin) install(TARGETS ${EXE_FILE} RUNTIME DESTINATION ${OPTION_EXE_DIRECTORY}) -install(TARGETS IDAWin ARCHIVE DESTINATION bin) - -# # # # # # # # # # # # # # # # # -# smoke test as a python script, for bash test example, see NFsim/tests/smoke -# # # # # # # # # # # # # # # # # -enable_testing() -set(test_dir ${CMAKE_CURRENT_SOURCE_DIR}/tests/smoke) -include(GoogleTest) -add_test(NAME ${EXE_FILE}_smoke COMMAND ${python_cmd} ${test_dir}/smoke.py ${test_sundials_exe} WORKING_DIRECTORY ${test_dir}) -add_executable(hello_test hello_test.cpp) -target_link_libraries(hello_test GTest::gtest_main) -gtest_discover_tests(hello_test) \ No newline at end of file +install(TARGETS IDAWin ARCHIVE DESTINATION bin) \ No newline at end of file diff --git a/IDAWin/SundialsSolverInterface.cpp b/IDAWin/SundialsSolverInterface.cpp new file mode 100644 index 000000000..ae3b8fdeb --- /dev/null +++ b/IDAWin/SundialsSolverInterface.cpp @@ -0,0 +1,67 @@ +// +// Created by Logan Drescher on 11/12/25. +// +// Messaging +#include "SundialsSolverInterface.h" +#include "VCellSundialsSolver.h" +#include "VCellSolverFactory.h" + +// included referenced in commented out code: +#include +#include +#include + +#define CVODE_SOLVER "CVODE" +#define IDA_SOLVER "IDA" + +void activateSolver(std::ifstream& inputFileStream, FILE* outputFile, int taskID) { + // std::string solver; + // + // while (!inputFileStream.eof()) { // Note break statement if "SOLVER" encountered + // std::string nextToken; + // inputFileStream >> nextToken; + // if (nextToken.empty()) continue; + // if (nextToken[0] == '#') getline(inputFileStream, nextToken); + // else if (nextToken == "JMS_PARAM_BEGIN") { + // loadJMSInfo(inputFileStream, taskID); + // #ifdef USE_MESSAGING + // SimulationMessaging::getInstVar()->start(); // start the thread + // #endif + // } else if (nextToken == "SOLVER") { + // inputFileStream >> solver; + // break; + // } + // } + // #ifdef USE_MESSAGING + // // should only happen during testing for solver compiled with messaging but run locally. + // if (SimulationMessaging::getInstVar() == nullptr) { SimulationMessaging::create(); } + // #endif + // + // if (solver.empty()) { throw "Solver not defined "; } + // VCellSundialsSolver *vss = nullptr; + // + // if (solver == IDA_SOLVER) { + // vss = new VCellIDASolver(); + // } else if (solver == CVODE_SOLVER) { + // vss = new VCellCVodeSolver(); + // } else { + // std::stringstream ss; + // ss << "Solver " << solver << " not defined!"; + // throw ss.str(); + // } + // + // vss->solve(nullptr, true, outputFile, VCellSundialsSolver::checkStopRequested); + // delete vss; + + // Check if we have messaging and a taskID of -1; if so; fail now. + + #ifdef USE_MESSAGING + if (taskID < 0) { + throw std::runtime_error("task id of value: " + std::to_string(taskID) + " not acceptable when this library is built with messaging"); + } + #endif + + VCellSolver* targetSolver = VCellSolverFactory::produceVCellSolver(inputFileStream, taskID); + targetSolver->solve(nullptr, true, outputFile, VCellSundialsSolver::checkStopRequested); +} + diff --git a/IDAWin/SundialsSolverInterface.h b/IDAWin/SundialsSolverInterface.h new file mode 100644 index 000000000..a6cb969ff --- /dev/null +++ b/IDAWin/SundialsSolverInterface.h @@ -0,0 +1,11 @@ +// +// Created by Logan Drescher on 11/12/25. +// +#ifndef VCELL_ODE_NUMERICS_SUNDIALSSOLVERINTERFACE_H +#define VCELL_ODE_NUMERICS_SUNDIALSSOLVERINTERFACE_H + +#include + +void activateSolver(std::ifstream& inputFileStream, FILE* outputFile, int taskID); + +#endif //VCELL_ODE_NUMERICS_SUNDIALSSOLVERINTERFACE_H \ No newline at end of file diff --git a/IDAWin/SundialsSolverStandalone.cpp b/IDAWin/SundialsSolverStandalone.cpp index 07f4ecf8f..fb6da561b 100644 --- a/IDAWin/SundialsSolverStandalone.cpp +++ b/IDAWin/SundialsSolverStandalone.cpp @@ -8,19 +8,16 @@ #include #include // Local Includes -#include "VCellCVodeSolver.h" -#include "VCellIDASolver.h" +#include "SundialsSolverInterface.h" + #include "StoppedByUserException.h" #include #include -#include "VCellSolverFactory.h" #define CVODE_SOLVER "CVODE" #define IDA_SOLVER "IDA" int parseAndRunWithArgParse(int argc, char *argv[]); -void activateSolver(std::ifstream& inputFileStream, FILE* outputFile, int taskID); -void loadJMSInfo(std::istream &ifsInput, int taskID); void errExit(int returnCode, std::string &errorMsg); int main(int argc, char *argv[]) { @@ -87,50 +84,6 @@ int parseAndRunWithArgParse(int argc, char *argv[]) { return returnCode; } -void activateSolver(std::ifstream& inputFileStream, FILE* outputFile, int taskID) { - // std::string solver; - // - // while (!inputFileStream.eof()) { // Note break statement if "SOLVER" encountered - // std::string nextToken; - // inputFileStream >> nextToken; - // if (nextToken.empty()) continue; - // if (nextToken[0] == '#') getline(inputFileStream, nextToken); - // else if (nextToken == "JMS_PARAM_BEGIN") { - // loadJMSInfo(inputFileStream, taskID); - // #ifdef USE_MESSAGING - // SimulationMessaging::getInstVar()->start(); // start the thread - // #endif - // } else if (nextToken == "SOLVER") { - // inputFileStream >> solver; - // break; - // } - // } - // #ifdef USE_MESSAGING - // // should only happen during testing for solver compiled with messaging but run locally. - // if (SimulationMessaging::getInstVar() == nullptr) { SimulationMessaging::create(); } - // #endif - // - // if (solver.empty()) { throw "Solver not defined "; } - // VCellSundialsSolver *vss = nullptr; - // - // if (solver == IDA_SOLVER) { - // vss = new VCellIDASolver(); - // } else if (solver == CVODE_SOLVER) { - // vss = new VCellCVodeSolver(); - // } else { - // std::stringstream ss; - // ss << "Solver " << solver << " not defined!"; - // throw ss.str(); - // } - // - // vss->solve(nullptr, true, outputFile, VCellSundialsSolver::checkStopRequested); - // delete vss; - - - VCellSolver* targetSolver = VCellSolverFactory::produceVCellSolver(inputFileStream, taskID); - targetSolver->solve(nullptr, true, outputFile, VCellSundialsSolver::checkStopRequested); -} - void errExit(int returnCode, std::string &errorMsg) { #ifdef USE_MESSAGING if (returnCode != 0) { diff --git a/IDAWin/VCellSolverFactory.cpp b/IDAWin/VCellSolverFactory.cpp index 5a905d73e..558454e4b 100644 --- a/IDAWin/VCellSolverFactory.cpp +++ b/IDAWin/VCellSolverFactory.cpp @@ -14,7 +14,7 @@ #include "VCellCVodeSolver.h" #include "VCellIDASolver.h" #ifdef USE_MESSAGING -static void loadJMSInfo(std::istream &ifsInput, int taskID); +#include #endif // Forward Declarations static void readDiscontinuities (std::istream &inputStream, VCellSolverInputBreakdown& inputBreakdown); @@ -54,12 +54,7 @@ VCellSolverInputBreakdown VCellSolverFactory::parseInputFile(std::ifstream& inpu inputFileStream >> nextToken; if (nextToken.empty()) continue; if (nextToken[0] == '#') std::getline(inputFileStream, nextToken); - else if (nextToken == "JMS_PARAM_BEGIN") { - #ifdef USE_MESSAGING - loadJMSInfo(inputFileStream, taskID); - SimulationMessaging::getInstVar()->start(); // start the thread - #endif - } else if (nextToken == "SOLVER") { + else if (nextToken == "SOLVER") { std::string solverName; inputFileStream >> solverName; inputBreakdown.solverType = determineSolverType(solverName); @@ -112,10 +107,13 @@ VCellSolverInputBreakdown VCellSolverFactory::parseInputFile(std::ifstream& inpu } VCellSolverTypes VCellSolverFactory::determineSolverType(const std::string& solverName){ - if (solverName == "CVODE") return VCellSolverTypes::CVODE; - if (solverName == "IDA") return VCellSolverTypes::IDA; - if (solverName == "CSSS") return VCellSolverTypes::STEADY_STATE; // copasi-style steady-state - throw new std::runtime_error("Unknown solver type: `" + solverName + "`"); + if (solverName == "CVODE") + return VCellSolverTypes::CVODE; + if (solverName == "IDA") + return VCellSolverTypes::IDA; + if (solverName == "CSSS") + return VCellSolverTypes::STEADY_STATE; // copasi-style steady-state + throw std::runtime_error("Unknown solver type: `" + solverName + "`"); } void VCellSolverFactory::processEquations(std::ifstream& inputFileStream, VCellSolverInputBreakdown& inputBreakdown) { @@ -127,51 +125,6 @@ void VCellSolverFactory::processEquations(std::ifstream& inputFileStream, VCellS * Local Helper Functions * * * * * * * * * * * * * * * * * * * * * * * * * * * **/ -#ifdef USE_MESSAGING -static void loadJMSInfo(std::istream &ifsInput, int taskID) { - if (taskID < 0) { - SimulationMessaging::create(); - return; // No need to do any parsing - } - std::string broker; - std::string smqUserName; - std::string password; - std::string qName; - std::string topicName; - std::string vCellUsername; - int simKey, jobIndex; - - while (!ifsInput.eof()) { - std::string nextToken; - ifsInput >> nextToken; - if (nextToken.empty()) continue; - if (nextToken[0] == '#') { - // std::getline(ifsInput, nextToken); // Is this ignoring because of a comment? - ifsInput.ignore('\n'); - continue; - } - if (nextToken == "JMS_PARAM_END") { ifsInput.ignore(EOF); } else if ( - nextToken == "JMS_BROKER") { ifsInput >> broker; } else if ( - nextToken == "JMS_USER") { ifsInput >> smqUserName >> password; } else if ( - nextToken == "JMS_QUEUE") { ifsInput >> qName; } else if ( - nextToken == "JMS_TOPIC") { ifsInput >> topicName; } else if (nextToken == "VCELL_USER") { - ifsInput >> vCellUsername; - } else if (nextToken == "SIMULATION_KEY") { - ifsInput >> simKey; - continue; - } else if (nextToken == "JOB_INDEX") { - ifsInput >> jobIndex; - continue; - } - } - - SimulationMessaging::create(broker.c_str(), smqUserName.c_str(), - password.c_str(), qName.c_str(), topicName.c_str(), - vCellUsername.c_str(), simKey, jobIndex, taskID); - -} -#endif - static void readDiscontinuities(std::istream &inputStream, VCellSolverInputBreakdown& inputBreakdown) { VCellSolverInputBreakdown::DiscontinuityComponents discontinuityComponents; std::size_t numOfDiscontinuities; @@ -483,7 +436,10 @@ static void loadJMSInfo(std::istream &ifsInput, int taskID) { std::string vCellUsername; int simKey, jobIndex; - while (!ifsInput.eof()) { + while (true) { + if (ifsInput.eof()) + throw std::runtime_error("VCellSolverFactory::loadJMSInfo() reached end of file, but no `JMS_PARAM_END` reached!"); + std::string nextToken; ifsInput >> nextToken; if (nextToken.empty()) continue; @@ -492,19 +448,15 @@ static void loadJMSInfo(std::istream &ifsInput, int taskID) { ifsInput.ignore('\n'); continue; } - if (nextToken == "JMS_PARAM_END") { ifsInput.ignore(EOF); } else if ( - nextToken == "JMS_BROKER") { ifsInput >> broker; } else if ( - nextToken == "JMS_USER") { ifsInput >> smqUserName >> password; } else if ( - nextToken == "JMS_QUEUE") { ifsInput >> qName; } else if ( - nextToken == "JMS_TOPIC") { ifsInput >> topicName; } else if (nextToken == "VCELL_USER") { - ifsInput >> vCellUsername; - } else if (nextToken == "SIMULATION_KEY") { - ifsInput >> simKey; - continue; - } else if (nextToken == "JOB_INDEX") { - ifsInput >> jobIndex; - continue; - } + if (nextToken == "JMS_PARAM_END") break; // Non-error stop condition + + if (nextToken == "JMS_BROKER") ifsInput >> broker; + else if (nextToken == "JMS_USER") ifsInput >> smqUserName >> password; + else if (nextToken == "JMS_QUEUE") ifsInput >> qName; + else if (nextToken == "JMS_TOPIC") ifsInput >> topicName; + else if (nextToken == "VCELL_USER") ifsInput >> vCellUsername; + else if (nextToken == "SIMULATION_KEY") ifsInput >> simKey; + else if (nextToken == "JOB_INDEX") ifsInput >> jobIndex; } SimulationMessaging::create(broker.c_str(), smqUserName.c_str(), diff --git a/IDAWin/VCellSundialsSolver.h b/IDAWin/VCellSundialsSolver.h index 8b5d3b170..57a02d062 100644 --- a/IDAWin/VCellSundialsSolver.h +++ b/IDAWin/VCellSundialsSolver.h @@ -33,13 +33,13 @@ class VCellSundialsSolver : public VCellSolver { void configureFromInput(VCellSolverInputBreakdown& inputBreakdown) override; - OdeResultSet *getResultSet() const { return this->odeResultSet; } + [[nodiscard]] OdeResultSet *getResultSet() const { return this->odeResultSet; } [[nodiscard]] int getNumEquations() const { return this->NUM_EQUATIONS; } - VCell::Expression **getInitialConditionExpressions() const { return this->initialConditionExpressions; } + [[nodiscard]] VCell::Expression **getInitialConditionExpressions() const { return this->initialConditionExpressions; } void setStartingTime(const realtype newStartingTime) { this->STARTING_TIME = newStartingTime; } void setEndingTime(const realtype newEndingTime) { this->ENDING_TIME = newEndingTime; } //void setOutputTimes(int count, double* newOutputTimes); - SymbolTable *getSymbolTable() const { return this->defaultSymbolTable; } + [[nodiscard]] SymbolTable *getSymbolTable() const { return this->defaultSymbolTable; } static void checkStopRequested(double, long); diff --git a/IDAWin/hello_test.cpp b/IDAWin/hello_test.cpp deleted file mode 100644 index 5a57e138f..000000000 --- a/IDAWin/hello_test.cpp +++ /dev/null @@ -1,9 +0,0 @@ -#include - -// Demonstrate some basic assertions. -TEST(HelloTest, BasicAssertions) { - // Expect two strings not to be equal. - EXPECT_STRNE("hello", "world"); - // Expect equality. - EXPECT_EQ(7 * 6, 42); -} diff --git a/IDAWin/tests/smoke/smoke.py b/IDAWin/tests/smoke/smoke.py deleted file mode 100755 index 65f4d7fee..000000000 --- a/IDAWin/tests/smoke/smoke.py +++ /dev/null @@ -1,72 +0,0 @@ -#!/usr/bin/env python3 - -import os -import posixpath -import subprocess -import sys -from pathlib import Path - - -# function to numerically compare the contents of two text files to determine approximate equality -# each line is a space delimited list of numbers, compare them to within 8 significant figures -def compare_files(file1: Path, file2: Path, tolerance: float): - with open(file1, 'r') as f1, open(file2, 'r') as f2: - for line1, line2 in zip(f1, f2): - if (line1 := line1.strip()) == (line2 := line2.strip()): - continue - array1 = [float(x) for x in line1.split()] - array2 = [float(x) for x in line2.split()] - for x, y in zip(array1, array2): - if x != y and abs(x - y) > tolerance * max(abs(x), abs(y)): - return False - return True - - -# get the directory of this script -test_dir = os.path.dirname(os.path.realpath(__file__)) -# in the path replace \ with /, D:\ with /d/ -test_dir = test_dir.replace("\\", "/") -# tell os.path.join to use / as the path separator -os.path.sep = "/" -exe = sys.argv[1] - -print(f"test_dir: {test_dir}") -print(f"exe: {exe}") - -input_file = posixpath.join(test_dir, "SimID_1489333437_0_.cvodeInput") -output_file = posixpath.join(test_dir, "SimID_1489333437_0_.ida") -expected_output_file = posixpath.join(test_dir, "SimID_1489333437_0_.ida.expected") - -if not posixpath.exists(exe): - print(f"SundialsSolverStandalone_x64 executable {exe} not found. Exiting...") - sys.exit(1) - -if not posixpath.exists(input_file): - print(f"Input file {input_file} not found. Exiting...") - sys.exit(1) - -if not posixpath.exists(expected_output_file): - print(f"Expected output file {expected_output_file} not found. Exiting...") - sys.exit(1) - -command = [exe, input_file, output_file] -print(" ".join(command)) - -try: - subprocess.check_call(command) -except subprocess.CalledProcessError: - print("SundialsSolverStandalone_x64 failed to run. Exiting...") - sys.exit(1) - -# verify that the output files exist -if not os.path.isfile(output_file): - print(f"Output file {output_file} not found. Exiting...") - sys.exit(1) - -# verify that the output files match the expected output files -if not compare_files(file1=Path(output_file), file2=Path(expected_output_file), tolerance=1e-8): - print(f"Output file {output_file} does not match expected output {expected_output_file}. Exiting...") - sys.exit(1) - -print("SundialsSolverStandalone_x64 solver completed and solution matched expected output. Exiting...") -sys.exit(0) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 000000000..686c973b3 --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1,43 @@ +############################################# +# GoogleTest +############################################## +include(FetchContent) +FetchContent_Declare( + googletest + GIT_REPOSITORY https://github.com/google/googletest.git + GIT_TAG v1.17.0 +) +if (WINDOWS) + # For Windows: Prevent overriding the parent project's compiler/linker settings + set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) +endif() + +FetchContent_MakeAvailable(googletest) +include(GoogleTest) + +# Add test executables +add_executable(unit_tests + unit/smoke_test.cpp + unit/hello_test.cpp +) +target_link_libraries(unit_tests PRIVATE + IDAWin + GTest::gtest_main +) +gtest_discover_tests(unit_tests) + +target_compile_definitions(unit_tests PRIVATE + RESOURCE_DIR="${CMAKE_CURRENT_SOURCE_DIR}/unit/resources" +) + + +#add_executable(integration_tests +# integration/test_integration.cpp +#) +#target_link_libraries(integration_tests PRIVATE +# IDAWin +# GTest::gtest_main +#) +#gtest_discover_tests(integration_tests) + + diff --git a/tests/unit/hello_test.cpp b/tests/unit/hello_test.cpp new file mode 100644 index 000000000..2d418cc0e --- /dev/null +++ b/tests/unit/hello_test.cpp @@ -0,0 +1,9 @@ +#include + +// Demonstrate some basic assertions. +TEST(HelloTest, BasicAssertions) { + // Expect two strings not to be equal. + EXPECT_STRNE("hello", "world"); + // Expect equality. + EXPECT_EQ(7 * 6, 42); +} \ No newline at end of file diff --git a/IDAWin/tests/smoke/SimID_1489333437_0_.cvodeInput b/tests/unit/resources/SimID_1489333437_0_.cvodeInput similarity index 100% rename from IDAWin/tests/smoke/SimID_1489333437_0_.cvodeInput rename to tests/unit/resources/SimID_1489333437_0_.cvodeInput diff --git a/IDAWin/tests/smoke/SimID_1489333437_0_.functions b/tests/unit/resources/SimID_1489333437_0_.functions similarity index 100% rename from IDAWin/tests/smoke/SimID_1489333437_0_.functions rename to tests/unit/resources/SimID_1489333437_0_.functions diff --git a/IDAWin/tests/smoke/SimID_1489333437_0_.ida.expected b/tests/unit/resources/SimID_1489333437_0_.ida.expected similarity index 100% rename from IDAWin/tests/smoke/SimID_1489333437_0_.ida.expected rename to tests/unit/resources/SimID_1489333437_0_.ida.expected diff --git a/IDAWin/tests/smoke/SimID_1489333437_0_.log b/tests/unit/resources/SimID_1489333437_0_.log similarity index 100% rename from IDAWin/tests/smoke/SimID_1489333437_0_.log rename to tests/unit/resources/SimID_1489333437_0_.log diff --git a/IDAWin/tests/smoke/SimID_1489333437_0__0.simtask.xml b/tests/unit/resources/SimID_1489333437_0__0.simtask.xml similarity index 100% rename from IDAWin/tests/smoke/SimID_1489333437_0__0.simtask.xml rename to tests/unit/resources/SimID_1489333437_0__0.simtask.xml diff --git a/tests/unit/resources/SimID_1607235431_0_.cvodeInput b/tests/unit/resources/SimID_1607235431_0_.cvodeInput new file mode 100644 index 000000000..fcd890744 --- /dev/null +++ b/tests/unit/resources/SimID_1607235431_0_.cvodeInput @@ -0,0 +1,231 @@ +SOLVER CVODE +STARTING_TIME 0.0 +ENDING_TIME 500.0 +RELATIVE_TOLERANCE 1.0E-9 +ABSOLUTE_TOLERANCE 1.0E-9 +MAX_TIME_STEP 1.0 +KEEP_EVERY 1 +DISCONTINUITIES 200 +__D_B_98 (t >= 250.0); (-250.0 + t); +__D_B_37 (t <= 187.75510204081633); (-187.75510204081633 + t); +__D_B_138 (t >= 41.83673469387755); (-41.83673469387755 + t); +__D_B_118 (t >= 22.448979591836736); (-22.448979591836736 + t); +__D_B_122 (t >= 26.3265306122449); (-26.3265306122449 + t); +__D_B_30 (t >= 180.6122448979592); (-180.6122448979592 + t); +__D_B_165 (t <= 68.01020408163264); (-68.01020408163264 + t); +__D_B_180 (t >= 82.55102040816327); (-82.55102040816327 + t); +__D_B_77 (t <= 228.57142857142856); (-228.57142857142856 + t); +__D_B_13 (t <= 163.26530612244898); (-163.26530612244898 + t); +__D_B_28 (t >= 178.57142857142858); (-178.57142857142858 + t); +__D_B_137 (t <= 40.867346938775505); (-40.867346938775505 + t); +__D_B_89 (t <= 240.81632653061223); (-240.81632653061223 + t); +__D_B_198 (t >= 100.0); (-100.0 + t); +__D_B_142 (t >= 45.714285714285715); (-45.714285714285715 + t); +__D_B_42 (t >= 192.85714285714286); (-192.85714285714286 + t); +__D_B_35 (t <= 185.71428571428572); (-185.71428571428572 + t); +__D_B_50 (t >= 201.0204081632653); (-201.0204081632653 + t); +__D_B_179 (t <= 81.58163265306122); (-81.58163265306122 + t); +__D_B_31 (t <= 181.6326530612245); (-181.6326530612245 + t); +__D_B_67 (t <= 218.3673469387755); (-218.3673469387755 + t); +__D_B_164 (t >= 67.0408163265306); (-67.0408163265306 + t); +__D_B_40 (t >= 190.81632653061223); (-190.81632653061223 + t); +__D_B_29 (t <= 179.59183673469389); (-179.59183673469389 + t); +__D_B_45 (t <= 195.91836734693877); (-195.91836734693877 + t); +__D_B_66 (t >= 217.3469387755102); (-217.3469387755102 + t); +__D_B_15 (t <= 165.30612244897958); (-165.30612244897958 + t); +__D_B_47 (t <= 197.9591836734694); (-197.9591836734694 + t); +__D_B_130 (t >= 34.08163265306122); (-34.08163265306122 + t); +__D_B_147 (t <= 50.56122448979591); (-50.56122448979591 + t); +__D_B_182 (t >= 84.48979591836735); (-84.48979591836735 + t); +__D_B_156 (t >= 59.285714285714285); (-59.285714285714285 + t); +__D_B_9 (t <= 159.18367346938774); (-159.18367346938774 + t); +__D_B_60 (t >= 211.22448979591837); (-211.22448979591837 + t); +__D_B_82 (t >= 233.67346938775512); (-233.67346938775512 + t); +__D_B_22 (t >= 172.44897959183675); (-172.44897959183675 + t); +__D_B_51 (t <= 202.0408163265306); (-202.0408163265306 + t); +__D_B_189 (t <= 91.27551020408163); (-91.27551020408163 + t); +__D_B_91 (t <= 242.85714285714286); (-242.85714285714286 + t); +__D_B_143 (t <= 46.68367346938775); (-46.68367346938775 + t); +__D_B_117 (t <= 21.479591836734684); (-21.479591836734684 + t); +__D_B_73 (t <= 224.48979591836735); (-224.48979591836735 + t); +__D_B_12 (t >= 162.24489795918367); (-162.24489795918367 + t); +__D_B_162 (t >= 65.10204081632654); (-65.10204081632654 + t); +__D_B_184 (t >= 86.42857142857143); (-86.42857142857143 + t); +__D_B_109 (t <= 13.724489795918359); (-13.724489795918359 + t); +__D_B_16 (t >= 166.3265306122449); (-166.3265306122449 + t); +__D_B_36 (t >= 186.73469387755102); (-186.73469387755102 + t); +__D_B_86 (t >= 237.75510204081633); (-237.75510204081633 + t); +__D_B_135 (t <= 38.92857142857142); (-38.92857142857142 + t); +__D_B_174 (t >= 76.73469387755102); (-76.73469387755102 + t); +__D_B_145 (t <= 48.62244897959183); (-48.62244897959183 + t); +__D_B_140 (t >= 43.775510204081634); (-43.775510204081634 + t); +__D_B_111 (t <= 15.663265306122442); (-15.663265306122442 + t); +__D_B_110 (t >= 14.693877551020408); (-14.693877551020408 + t); +__D_B_103 (t <= 7.908163265306115); (-7.908163265306115 + t); +__D_B_161 (t <= 64.13265306122449); (-64.13265306122449 + t); +__D_B_194 (t >= 96.12244897959184); (-96.12244897959184 + t); +__D_B_8 (t >= 158.16326530612244); (-158.16326530612244 + t); +__D_B_139 (t <= 42.806122448979586); (-42.806122448979586 + t); +__D_B_1 (t <= 151.0204081632653); (-151.0204081632653 + t); +__D_B_62 (t >= 213.26530612244898); (-213.26530612244898 + t); +__D_B_76 (t >= 227.55102040816325); (-227.55102040816325 + t); +__D_B_85 (t <= 236.73469387755102); (-236.73469387755102 + t); +__D_B_128 (t >= 32.14285714285714); (-32.14285714285714 + t); +__D_B_61 (t <= 212.24489795918367); (-212.24489795918367 + t); +__D_B_33 (t <= 183.6734693877551); (-183.6734693877551 + t); +__D_B_159 (t <= 62.1938775510204); (-62.1938775510204 + t); +__D_B_23 (t <= 173.46938775510205); (-173.46938775510205 + t); +__D_B_144 (t >= 47.6530612244898); (-47.6530612244898 + t); +__D_B_7 (t <= 157.14285714285714); (-157.14285714285714 + t); +__D_B_21 (t <= 171.42857142857142); (-171.42857142857142 + t); +__D_B_136 (t >= 39.89795918367347); (-39.89795918367347 + t); +__D_B_52 (t >= 203.0612244897959); (-203.0612244897959 + t); +__D_B_10 (t >= 160.20408163265307); (-160.20408163265307 + t); +__D_B_2 (t >= 152.0408163265306); (-152.0408163265306 + t); +__D_B_157 (t <= 60.25510204081632); (-60.25510204081632 + t); +__D_B_48 (t >= 198.9795918367347); (-198.9795918367347 + t); +__D_B_58 (t >= 209.18367346938777); (-209.18367346938777 + t); +__D_B_24 (t >= 174.48979591836735); (-174.48979591836735 + t); +__D_B_87 (t <= 238.77551020408163); (-238.77551020408163 + t); +__D_B_146 (t >= 49.59183673469388); (-49.59183673469388 + t); +__D_B_106 (t >= 10.816326530612244); (-10.816326530612244 + t); +__D_B_53 (t <= 204.0816326530612); (-204.0816326530612 + t); +__D_B_54 (t >= 205.10204081632654); (-205.10204081632654 + t); +__D_B_148 (t >= 51.53061224489796); (-51.53061224489796 + t); +__D_B_59 (t <= 210.20408163265307); (-210.20408163265307 + t); +__D_B_108 (t >= 12.755102040816325); (-12.755102040816325 + t); +__D_B_56 (t >= 207.14285714285714); (-207.14285714285714 + t); +__D_B_169 (t <= 71.88775510204081); (-71.88775510204081 + t); +__D_B_101 (t <= 5.969387755102034); (-5.969387755102034 + t); +__D_B_4 (t >= 154.08163265306123); (-154.08163265306123 + t); +__D_B_32 (t >= 182.6530612244898); (-182.6530612244898 + t); +__D_B_74 (t >= 225.51020408163265); (-225.51020408163265 + t); +__D_B_114 (t >= 18.57142857142857); (-18.57142857142857 + t); +__D_B_72 (t >= 223.46938775510205); (-223.46938775510205 + t); +__D_B_119 (t <= 23.41836734693877); (-23.41836734693877 + t); +__D_B_113 (t <= 17.60204081632652); (-17.60204081632652 + t); +__D_B_181 (t <= 83.5204081632653); (-83.5204081632653 + t); +__D_B_90 (t >= 241.83673469387756); (-241.83673469387756 + t); +__D_B_5 (t <= 155.10204081632654); (-155.10204081632654 + t); +__D_B_191 (t <= 93.21428571428571); (-93.21428571428571 + t); +__D_B_133 (t <= 36.989795918367335); (-36.989795918367335 + t); +__D_B_17 (t <= 167.3469387755102); (-167.3469387755102 + t); +__D_B_27 (t <= 177.55102040816325); (-177.55102040816325 + t); +__D_B_177 (t <= 79.64285714285714); (-79.64285714285714 + t); +__D_B_183 (t <= 85.45918367346938); (-85.45918367346938 + t); +__D_B_188 (t >= 90.3061224489796); (-90.3061224489796 + t); +__D_B_132 (t >= 36.0204081632653); (-36.0204081632653 + t); +__D_B_152 (t >= 55.40816326530612); (-55.40816326530612 + t); +__D_B_149 (t <= 52.49999999999999); (-52.49999999999999 + t); +__D_B_79 (t <= 230.6122448979592); (-230.6122448979592 + t); +__D_B_116 (t >= 20.51020408163265); (-20.51020408163265 + t); +__D_B_18 (t >= 168.3673469387755); (-168.3673469387755 + t); +__D_B_96 (t >= 247.9591836734694); (-247.9591836734694 + t); +__D_B_160 (t >= 63.16326530612245); (-63.16326530612245 + t); +__D_B_187 (t <= 89.33673469387755); (-89.33673469387755 + t); +__D_B_55 (t <= 206.12244897959184); (-206.12244897959184 + t); +__D_B_70 (t >= 221.42857142857144); (-221.42857142857144 + t); +__D_B_95 (t <= 246.9387755102041); (-246.9387755102041 + t); +__D_B_38 (t >= 188.77551020408163); (-188.77551020408163 + t); +__D_B_178 (t >= 80.61224489795919); (-80.61224489795919 + t); +__D_B_26 (t >= 176.53061224489795); (-176.53061224489795 + t); +__D_B_158 (t >= 61.224489795918366); (-61.224489795918366 + t); +__D_B_175 (t <= 77.70408163265306); (-77.70408163265306 + t); +__D_B_99 (t <= 251.0204081632653); (-251.0204081632653 + t); +__D_B_107 (t <= 11.785714285714278); (-11.785714285714278 + t); +__D_B_19 (t <= 169.3877551020408); (-169.3877551020408 + t); +__D_B_168 (t >= 70.91836734693878); (-70.91836734693878 + t); +__D_B_3 (t <= 153.0612244897959); (-153.0612244897959 + t); +__D_B_39 (t <= 189.79591836734693); (-189.79591836734693 + t); +__D_B_171 (t <= 73.8265306122449); (-73.8265306122449 + t); +__D_B_25 (t <= 175.51020408163265); (-175.51020408163265 + t); +__D_B_167 (t <= 69.94897959183673); (-69.94897959183673 + t); +__D_B_6 (t >= 156.12244897959184); (-156.12244897959184 + t); +__D_B_176 (t >= 78.6734693877551); (-78.6734693877551 + t); +__D_B_123 (t <= 27.295918367346932); (-27.295918367346932 + t); +__D_B_155 (t <= 58.31632653061224); (-58.31632653061224 + t); +__D_B_141 (t <= 44.74489795918367); (-44.74489795918367 + t); +__D_B_197 (t <= 99.03061224489795); (-99.03061224489795 + t); +__D_B_64 (t >= 215.30612244897958); (-215.30612244897958 + t); +__D_B_170 (t >= 72.85714285714286); (-72.85714285714286 + t); +__D_B_112 (t >= 16.632653061224488); (-16.632653061224488 + t); +__D_B_151 (t <= 54.438775510204074); (-54.438775510204074 + t); +__D_B_94 (t >= 245.9183673469388); (-245.9183673469388 + t); +__D_B_192 (t >= 94.18367346938776); (-94.18367346938776 + t); +__D_B_125 (t <= 29.234693877551013); (-29.234693877551013 + t); +__D_B_153 (t <= 56.377551020408156); (-56.377551020408156 + t); +__D_B_100 (t >= 5.0); (-5.0 + t); +__D_B_69 (t <= 220.40816326530611); (-220.40816326530611 + t); +__D_B_84 (t >= 235.71428571428572); (-235.71428571428572 + t); +__D_B_150 (t >= 53.46938775510204); (-53.46938775510204 + t); +__D_B_131 (t <= 35.051020408163254); (-35.051020408163254 + t); +__D_B_44 (t >= 194.89795918367346); (-194.89795918367346 + t); +__D_B_41 (t <= 191.83673469387753); (-191.83673469387753 + t); +__D_B_49 (t <= 200.0); (-200.0 + t); +__D_B_0 (t >= 150.0); (-150.0 + t); +__D_B_75 (t <= 226.53061224489795); (-226.53061224489795 + t); +__D_B_88 (t >= 239.79591836734693); (-239.79591836734693 + t); +__D_B_196 (t >= 98.06122448979592); (-98.06122448979592 + t); +__D_B_105 (t <= 9.846938775510196); (-9.846938775510196 + t); +__D_B_63 (t <= 214.28571428571428); (-214.28571428571428 + t); +__D_B_80 (t >= 231.6326530612245); (-231.6326530612245 + t); +__D_B_83 (t <= 234.69387755102042); (-234.69387755102042 + t); +__D_B_93 (t <= 244.89795918367346); (-244.89795918367346 + t); +__D_B_57 (t <= 208.16326530612244); (-208.16326530612244 + t); +__D_B_127 (t <= 31.173469387755095); (-31.173469387755095 + t); +__D_B_185 (t <= 87.39795918367346); (-87.39795918367346 + t); +__D_B_115 (t <= 19.540816326530603); (-19.540816326530603 + t); +__D_B_71 (t <= 222.44897959183675); (-222.44897959183675 + t); +__D_B_172 (t >= 74.79591836734694); (-74.79591836734694 + t); +__D_B_199 (t <= 100.96938775510203); (-100.96938775510203 + t); +__D_B_186 (t >= 88.36734693877551); (-88.36734693877551 + t); +__D_B_129 (t <= 33.11224489795917); (-33.11224489795917 + t); +__D_B_14 (t >= 164.28571428571428); (-164.28571428571428 + t); +__D_B_134 (t >= 37.95918367346939); (-37.95918367346939 + t); +__D_B_68 (t >= 219.3877551020408); (-219.3877551020408 + t); +__D_B_193 (t <= 95.15306122448979); (-95.15306122448979 + t); +__D_B_11 (t <= 161.22448979591837); (-161.22448979591837 + t); +__D_B_163 (t <= 66.07142857142857); (-66.07142857142857 + t); +__D_B_124 (t >= 28.26530612244898); (-28.26530612244898 + t); +__D_B_20 (t >= 170.40816326530611); (-170.40816326530611 + t); +__D_B_43 (t <= 193.87755102040816); (-193.87755102040816 + t); +__D_B_102 (t >= 6.938775510204081); (-6.938775510204081 + t); +__D_B_166 (t >= 68.9795918367347); (-68.9795918367347 + t); +__D_B_65 (t <= 216.32653061224488); (-216.32653061224488 + t); +__D_B_92 (t >= 243.87755102040816); (-243.87755102040816 + t); +__D_B_120 (t >= 24.387755102040817); (-24.387755102040817 + t); +__D_B_154 (t >= 57.3469387755102); (-57.3469387755102 + t); +__D_B_46 (t >= 196.9387755102041); (-196.9387755102041 + t); +__D_B_34 (t >= 184.69387755102042); (-184.69387755102042 + t); +__D_B_190 (t >= 92.24489795918367); (-92.24489795918367 + t); +__D_B_97 (t <= 248.9795918367347); (-248.9795918367347 + t); +__D_B_121 (t <= 25.35714285714285); (-25.35714285714285 + t); +__D_B_78 (t >= 229.59183673469389); (-229.59183673469389 + t); +__D_B_173 (t <= 75.76530612244898); (-75.76530612244898 + t); +__D_B_195 (t <= 97.09183673469387); (-97.09183673469387 + t); +__D_B_126 (t >= 30.20408163265306); (-30.20408163265306 + t); +__D_B_81 (t <= 232.6530612244898); (-232.6530612244898 + t); +__D_B_104 (t >= 8.877551020408163); (-8.877551020408163 + t); +EVENTS 2 +EVENT event1 +TRIGGER (((__D_B_0 == 1.0) && (__D_B_1 == 1.0)) || ((__D_B_2 == 1.0) && (__D_B_3 == 1.0)) || ((__D_B_4 == 1.0) && (__D_B_5 == 1.0)) || ((__D_B_6 == 1.0) && (__D_B_7 == 1.0)) || ((__D_B_8 == 1.0) && (__D_B_9 == 1.0)) || ((__D_B_10 == 1.0) && (__D_B_11 == 1.0)) || ((__D_B_12 == 1.0) && (__D_B_13 == 1.0)) || ((__D_B_14 == 1.0) && (__D_B_15 == 1.0)) || ((__D_B_16 == 1.0) && (__D_B_17 == 1.0)) || ((__D_B_18 == 1.0) && (__D_B_19 == 1.0)) || ((__D_B_20 == 1.0) && (__D_B_21 == 1.0)) || ((__D_B_22 == 1.0) && (__D_B_23 == 1.0)) || ((__D_B_24 == 1.0) && (__D_B_25 == 1.0)) || ((__D_B_26 == 1.0) && (__D_B_27 == 1.0)) || ((__D_B_28 == 1.0) && (__D_B_29 == 1.0)) || ((__D_B_30 == 1.0) && (__D_B_31 == 1.0)) || ((__D_B_32 == 1.0) && (__D_B_33 == 1.0)) || ((__D_B_34 == 1.0) && (__D_B_35 == 1.0)) || ((__D_B_36 == 1.0) && (__D_B_37 == 1.0)) || ((__D_B_38 == 1.0) && (__D_B_39 == 1.0)) || ((__D_B_40 == 1.0) && (__D_B_41 == 1.0)) || ((__D_B_42 == 1.0) && (__D_B_43 == 1.0)) || ((__D_B_44 == 1.0) && (__D_B_45 == 1.0)) || ((__D_B_46 == 1.0) && (__D_B_47 == 1.0)) || ((__D_B_48 == 1.0) && (__D_B_49 == 1.0)) || ((__D_B_50 == 1.0) && (__D_B_51 == 1.0)) || ((__D_B_52 == 1.0) && (__D_B_53 == 1.0)) || ((__D_B_54 == 1.0) && (__D_B_55 == 1.0)) || ((__D_B_56 == 1.0) && (__D_B_57 == 1.0)) || ((__D_B_58 == 1.0) && (__D_B_59 == 1.0)) || ((__D_B_60 == 1.0) && (__D_B_61 == 1.0)) || ((__D_B_62 == 1.0) && (__D_B_63 == 1.0)) || ((__D_B_64 == 1.0) && (__D_B_65 == 1.0)) || ((__D_B_66 == 1.0) && (__D_B_67 == 1.0)) || ((__D_B_68 == 1.0) && (__D_B_69 == 1.0)) || ((__D_B_70 == 1.0) && (__D_B_71 == 1.0)) || ((__D_B_72 == 1.0) && (__D_B_73 == 1.0)) || ((__D_B_74 == 1.0) && (__D_B_75 == 1.0)) || ((__D_B_76 == 1.0) && (__D_B_77 == 1.0)) || ((__D_B_78 == 1.0) && (__D_B_79 == 1.0)) || ((__D_B_80 == 1.0) && (__D_B_81 == 1.0)) || ((__D_B_82 == 1.0) && (__D_B_83 == 1.0)) || ((__D_B_84 == 1.0) && (__D_B_85 == 1.0)) || ((__D_B_86 == 1.0) && (__D_B_87 == 1.0)) || ((__D_B_88 == 1.0) && (__D_B_89 == 1.0)) || ((__D_B_90 == 1.0) && (__D_B_91 == 1.0)) || ((__D_B_92 == 1.0) && (__D_B_93 == 1.0)) || ((__D_B_94 == 1.0) && (__D_B_95 == 1.0)) || ((__D_B_96 == 1.0) && (__D_B_97 == 1.0)) || ((__D_B_98 == 1.0) && (__D_B_99 == 1.0))); +EVENTASSIGNMENTS 1 +0 (-5.0 + A); +EVENT event0 +TRIGGER (((__D_B_100 == 1.0) && (__D_B_101 == 1.0)) || ((__D_B_102 == 1.0) && (__D_B_103 == 1.0)) || ((__D_B_104 == 1.0) && (__D_B_105 == 1.0)) || ((__D_B_106 == 1.0) && (__D_B_107 == 1.0)) || ((__D_B_108 == 1.0) && (__D_B_109 == 1.0)) || ((__D_B_110 == 1.0) && (__D_B_111 == 1.0)) || ((__D_B_112 == 1.0) && (__D_B_113 == 1.0)) || ((__D_B_114 == 1.0) && (__D_B_115 == 1.0)) || ((__D_B_116 == 1.0) && (__D_B_117 == 1.0)) || ((__D_B_118 == 1.0) && (__D_B_119 == 1.0)) || ((__D_B_120 == 1.0) && (__D_B_121 == 1.0)) || ((__D_B_122 == 1.0) && (__D_B_123 == 1.0)) || ((__D_B_124 == 1.0) && (__D_B_125 == 1.0)) || ((__D_B_126 == 1.0) && (__D_B_127 == 1.0)) || ((__D_B_128 == 1.0) && (__D_B_129 == 1.0)) || ((__D_B_130 == 1.0) && (__D_B_131 == 1.0)) || ((__D_B_132 == 1.0) && (__D_B_133 == 1.0)) || ((__D_B_134 == 1.0) && (__D_B_135 == 1.0)) || ((__D_B_136 == 1.0) && (__D_B_137 == 1.0)) || ((__D_B_138 == 1.0) && (__D_B_139 == 1.0)) || ((__D_B_140 == 1.0) && (__D_B_141 == 1.0)) || ((__D_B_142 == 1.0) && (__D_B_143 == 1.0)) || ((__D_B_144 == 1.0) && (__D_B_145 == 1.0)) || ((__D_B_146 == 1.0) && (__D_B_147 == 1.0)) || ((__D_B_148 == 1.0) && (__D_B_149 == 1.0)) || ((__D_B_150 == 1.0) && (__D_B_151 == 1.0)) || ((__D_B_152 == 1.0) && (__D_B_153 == 1.0)) || ((__D_B_154 == 1.0) && (__D_B_155 == 1.0)) || ((__D_B_156 == 1.0) && (__D_B_157 == 1.0)) || ((__D_B_158 == 1.0) && (__D_B_159 == 1.0)) || ((__D_B_160 == 1.0) && (__D_B_161 == 1.0)) || ((__D_B_162 == 1.0) && (__D_B_163 == 1.0)) || ((__D_B_164 == 1.0) && (__D_B_165 == 1.0)) || ((__D_B_166 == 1.0) && (__D_B_167 == 1.0)) || ((__D_B_168 == 1.0) && (__D_B_169 == 1.0)) || ((__D_B_170 == 1.0) && (__D_B_171 == 1.0)) || ((__D_B_172 == 1.0) && (__D_B_173 == 1.0)) || ((__D_B_174 == 1.0) && (__D_B_175 == 1.0)) || ((__D_B_176 == 1.0) && (__D_B_177 == 1.0)) || ((__D_B_178 == 1.0) && (__D_B_179 == 1.0)) || ((__D_B_180 == 1.0) && (__D_B_181 == 1.0)) || ((__D_B_182 == 1.0) && (__D_B_183 == 1.0)) || ((__D_B_184 == 1.0) && (__D_B_185 == 1.0)) || ((__D_B_186 == 1.0) && (__D_B_187 == 1.0)) || ((__D_B_188 == 1.0) && (__D_B_189 == 1.0)) || ((__D_B_190 == 1.0) && (__D_B_191 == 1.0)) || ((__D_B_192 == 1.0) && (__D_B_193 == 1.0)) || ((__D_B_194 == 1.0) && (__D_B_195 == 1.0)) || ((__D_B_196 == 1.0) && (__D_B_197 == 1.0)) || ((__D_B_198 == 1.0) && (__D_B_199 == 1.0))); +EVENTASSIGNMENTS 1 +0 (5.0 + A); +NUM_EQUATIONS 6 +ODE A INIT 10.0; + RATE 0.0; +ODE OCT4 INIT 0.01; + RATE ((1000.0 * (1.0E-4 + A + (0.01 * OCT4_SOX2) + (0.2 * NANOG * OCT4_SOX2)) / (1000.0001 + (1.1 * A) + OCT4_SOX2 + (0.7 * NANOG * OCT4_SOX2))) - OCT4 - ((0.05 * OCT4 * SOX2) - (0.001 * OCT4_SOX2))); +ODE SOX2 INIT 0.01; + RATE ( - ((0.05 * OCT4 * SOX2) - (0.001 * OCT4_SOX2)) + (1000.0 * (1.0E-4 + A + (0.01 * OCT4_SOX2) + (0.2 * NANOG * OCT4_SOX2)) / (1000.0001 + (1.1 * A) + OCT4_SOX2 + (0.7 * NANOG * OCT4_SOX2))) - SOX2); +ODE NANOG INIT 0.01; + RATE (((1.0E-4 + (0.005 * OCT4_SOX2) + (0.1 * OCT4_SOX2 * NANOG)) / (1.0000001 + (9.95E-4 * OCT4_SOX2) + (0.001 * OCT4_SOX2 * NANOG))) - NANOG); +ODE OCT4_SOX2 INIT 0.1; + RATE ((0.05 * OCT4 * SOX2) - (0.001 * OCT4_SOX2) - (5.0 * OCT4_SOX2)); +ODE Protein INIT 0.0; + RATE ((1000.0 * (1.0E-4 + (0.1 * OCT4_SOX2)) / (1000.0001 + OCT4_SOX2 + (1000.0 * NANOG * OCT4_SOX2))) - (0.01 * Protein)); + diff --git a/tests/unit/resources/SimID_1607235431_0_.functions b/tests/unit/resources/SimID_1607235431_0_.functions new file mode 100644 index 000000000..94f84ae79 --- /dev/null +++ b/tests/unit/resources/SimID_1607235431_0_.functions @@ -0,0 +1,23 @@ +##--------------------------------------------- +## /Users/logandrescher/.vcell/simdata/temp/SimID_1607235431_0_.functions +##--------------------------------------------- + +Compartment::degradation; 0.0; ; Nonspatial_VariableType; false +event0.triggerFunction; (((t >= 5.0) && (t <= 5.969387755102034)) || ((t >= 6.938775510204081) && (t <= 7.908163265306115)) || ((t >= 8.877551020408163) && (t <= 9.846938775510196)) || ((t >= 10.816326530612244) && (t <= 11.785714285714278)) || ((t >= 12.755102040816325) && (t <= 13.724489795918359)) || ((t >= 14.693877551020408) && (t <= 15.663265306122442)) || ((t >= 16.632653061224488) && (t <= 17.60204081632652)) || ((t >= 18.57142857142857) && (t <= 19.540816326530603)) || ((t >= 20.51020408163265) && (t <= 21.479591836734684)) || ((t >= 22.448979591836736) && (t <= 23.41836734693877)) || ((t >= 24.387755102040817) && (t <= 25.35714285714285)) || ((t >= 26.3265306122449) && (t <= 27.295918367346932)) || ((t >= 28.26530612244898) && (t <= 29.234693877551013)) || ((t >= 30.20408163265306) && (t <= 31.173469387755095)) || ((t >= 32.14285714285714) && (t <= 33.11224489795917)) || ((t >= 34.08163265306122) && (t <= 35.051020408163254)) || ((t >= 36.0204081632653) && (t <= 36.989795918367335)) || ((t >= 37.95918367346939) && (t <= 38.92857142857142)) || ((t >= 39.89795918367347) && (t <= 40.867346938775505)) || ((t >= 41.83673469387755) && (t <= 42.806122448979586)) || ((t >= 43.775510204081634) && (t <= 44.74489795918367)) || ((t >= 45.714285714285715) && (t <= 46.68367346938775)) || ((t >= 47.6530612244898) && (t <= 48.62244897959183)) || ((t >= 49.59183673469388) && (t <= 50.56122448979591)) || ((t >= 51.53061224489796) && (t <= 52.49999999999999)) || ((t >= 53.46938775510204) && (t <= 54.438775510204074)) || ((t >= 55.40816326530612) && (t <= 56.377551020408156)) || ((t >= 57.3469387755102) && (t <= 58.31632653061224)) || ((t >= 59.285714285714285) && (t <= 60.25510204081632)) || ((t >= 61.224489795918366) && (t <= 62.1938775510204)) || ((t >= 63.16326530612245) && (t <= 64.13265306122449)) || ((t >= 65.10204081632654) && (t <= 66.07142857142857)) || ((t >= 67.0408163265306) && (t <= 68.01020408163264)) || ((t >= 68.9795918367347) && (t <= 69.94897959183673)) || ((t >= 70.91836734693878) && (t <= 71.88775510204081)) || ((t >= 72.85714285714286) && (t <= 73.8265306122449)) || ((t >= 74.79591836734694) && (t <= 75.76530612244898)) || ((t >= 76.73469387755102) && (t <= 77.70408163265306)) || ((t >= 78.6734693877551) && (t <= 79.64285714285714)) || ((t >= 80.61224489795919) && (t <= 81.58163265306122)) || ((t >= 82.55102040816327) && (t <= 83.5204081632653)) || ((t >= 84.48979591836735) && (t <= 85.45918367346938)) || ((t >= 86.42857142857143) && (t <= 87.39795918367346)) || ((t >= 88.36734693877551) && (t <= 89.33673469387755)) || ((t >= 90.3061224489796) && (t <= 91.27551020408163)) || ((t >= 92.24489795918367) && (t <= 93.21428571428571)) || ((t >= 94.18367346938776) && (t <= 95.15306122448979)) || ((t >= 96.12244897959184) && (t <= 97.09183673469387)) || ((t >= 98.06122448979592) && (t <= 99.03061224489795)) || ((t >= 100.0) && (t <= 100.96938775510203))); ; Nonspatial_VariableType; false +event1.triggerFunction; (((t >= 150.0) && (t <= 151.0204081632653)) || ((t >= 152.0408163265306) && (t <= 153.0612244897959)) || ((t >= 154.08163265306123) && (t <= 155.10204081632654)) || ((t >= 156.12244897959184) && (t <= 157.14285714285714)) || ((t >= 158.16326530612244) && (t <= 159.18367346938774)) || ((t >= 160.20408163265307) && (t <= 161.22448979591837)) || ((t >= 162.24489795918367) && (t <= 163.26530612244898)) || ((t >= 164.28571428571428) && (t <= 165.30612244897958)) || ((t >= 166.3265306122449) && (t <= 167.3469387755102)) || ((t >= 168.3673469387755) && (t <= 169.3877551020408)) || ((t >= 170.40816326530611) && (t <= 171.42857142857142)) || ((t >= 172.44897959183675) && (t <= 173.46938775510205)) || ((t >= 174.48979591836735) && (t <= 175.51020408163265)) || ((t >= 176.53061224489795) && (t <= 177.55102040816325)) || ((t >= 178.57142857142858) && (t <= 179.59183673469389)) || ((t >= 180.6122448979592) && (t <= 181.6326530612245)) || ((t >= 182.6530612244898) && (t <= 183.6734693877551)) || ((t >= 184.69387755102042) && (t <= 185.71428571428572)) || ((t >= 186.73469387755102) && (t <= 187.75510204081633)) || ((t >= 188.77551020408163) && (t <= 189.79591836734693)) || ((t >= 190.81632653061223) && (t <= 191.83673469387753)) || ((t >= 192.85714285714286) && (t <= 193.87755102040816)) || ((t >= 194.89795918367346) && (t <= 195.91836734693877)) || ((t >= 196.9387755102041) && (t <= 197.9591836734694)) || ((t >= 198.9795918367347) && (t <= 200.0)) || ((t >= 201.0204081632653) && (t <= 202.0408163265306)) || ((t >= 203.0612244897959) && (t <= 204.0816326530612)) || ((t >= 205.10204081632654) && (t <= 206.12244897959184)) || ((t >= 207.14285714285714) && (t <= 208.16326530612244)) || ((t >= 209.18367346938777) && (t <= 210.20408163265307)) || ((t >= 211.22448979591837) && (t <= 212.24489795918367)) || ((t >= 213.26530612244898) && (t <= 214.28571428571428)) || ((t >= 215.30612244897958) && (t <= 216.32653061224488)) || ((t >= 217.3469387755102) && (t <= 218.3673469387755)) || ((t >= 219.3877551020408) && (t <= 220.40816326530611)) || ((t >= 221.42857142857144) && (t <= 222.44897959183675)) || ((t >= 223.46938775510205) && (t <= 224.48979591836735)) || ((t >= 225.51020408163265) && (t <= 226.53061224489795)) || ((t >= 227.55102040816325) && (t <= 228.57142857142856)) || ((t >= 229.59183673469389) && (t <= 230.6122448979592)) || ((t >= 231.6326530612245) && (t <= 232.6530612244898)) || ((t >= 233.67346938775512) && (t <= 234.69387755102042)) || ((t >= 235.71428571428572) && (t <= 236.73469387755102)) || ((t >= 237.75510204081633) && (t <= 238.77551020408163)) || ((t >= 239.79591836734693) && (t <= 240.81632653061223)) || ((t >= 241.83673469387756) && (t <= 242.85714285714286)) || ((t >= 243.87755102040816) && (t <= 244.89795918367346)) || ((t >= 245.9183673469388) && (t <= 246.9387755102041)) || ((t >= 247.9591836734694) && (t <= 248.9795918367347)) || ((t >= 250.0) && (t <= 251.0204081632653))); ; Nonspatial_VariableType; false +Compartment::LumpedJ_J0; (1000.0 * (1.0E-4 + A + (0.01 * OCT4_SOX2) + (0.2 * NANOG * OCT4_SOX2)) / (1000.0001 + (1.1 * A) + OCT4_SOX2 + (0.7 * NANOG * OCT4_SOX2))); ; Nonspatial_VariableType; false +Compartment::LumpedJ_J1; OCT4; ; Nonspatial_VariableType; false +Compartment::LumpedJ_J2; ((1.0E-4 + (0.005 * OCT4_SOX2) + (0.1 * OCT4_SOX2 * NANOG)) / (1.0000001 + (9.95E-4 * OCT4_SOX2) + (0.001 * OCT4_SOX2 * NANOG))); ; Nonspatial_VariableType; false +Compartment::LumpedJ_J3; NANOG; ; Nonspatial_VariableType; false +Compartment::LumpedJ_J4; ((0.05 * OCT4 * SOX2) - (0.001 * OCT4_SOX2)); ; Nonspatial_VariableType; false +Compartment::LumpedJ_J5; (5.0 * OCT4_SOX2); ; Nonspatial_VariableType; false +Compartment::LumpedJ_J6; (1000.0 * (1.0E-4 + A + (0.01 * OCT4_SOX2) + (0.2 * NANOG * OCT4_SOX2)) / (1000.0001 + (1.1 * A) + OCT4_SOX2 + (0.7 * NANOG * OCT4_SOX2))); ; Nonspatial_VariableType; false +Compartment::LumpedJ_J7; SOX2; ; Nonspatial_VariableType; false +Compartment::LumpedJ_J8; (1000.0 * (1.0E-4 + (0.1 * OCT4_SOX2)) / (1000.0001 + OCT4_SOX2 + (1000.0 * NANOG * OCT4_SOX2))); ; Nonspatial_VariableType; false +Compartment::LumpedJ_J9; (0.01 * Protein); ; Nonspatial_VariableType; false +Compartment::NANOG_Gene; 0.0; ; Nonspatial_VariableType; false +Compartment::OCT4_Gene; 1.0; ; Nonspatial_VariableType; false +Compartment::p53; 0.0; ; Nonspatial_VariableType; false +Compartment::SOX2_Gene; 0.0; ; Nonspatial_VariableType; false +Compartment::targetGene; 0.01; ; Nonspatial_VariableType; false + diff --git a/tests/unit/resources/SimID_1607235431_0_.log b/tests/unit/resources/SimID_1607235431_0_.log new file mode 100644 index 000000000..89146ccf2 --- /dev/null +++ b/tests/unit/resources/SimID_1607235431_0_.log @@ -0,0 +1,4 @@ +IDAData logfile +IDAData text format version 1 +SimID_1607235431_0_.ida +KeepMost 1000 diff --git a/tests/unit/resources/SimID_1607235431_0__0.simtask.xml b/tests/unit/resources/SimID_1607235431_0__0.simtask.xml new file mode 100644 index 000000000..e41a76b03 --- /dev/null +++ b/tests/unit/resources/SimID_1607235431_0__0.simtask.xml @@ -0,0 +1,162 @@ + + + 96485.3321 + 9.64853321E-5 + 1.0E-9 + 6.02214179E11 + 3.141592653589793 + 8314.46261815 + 300.0 + 1.0 + 0.1 + 0.2 + 10.0 + 0.0 + 0.0011 + 0.001 + 7.0E-4 + 1.0 + 0.01 + 0.2 + 0.0011 + 0.001 + 7.0E-4 + 0.0 + 0.005 + 0.1 + 1.0E-4 + 1.0E-4 + 1.0E-4 + 1.0E-4 + 0.0 + 100.0 + 5.0 + 50.0 + 0.0 + 250.0 + 150.0 + 50.0 + 1000.0 + 0.001 + 9.95E-4 + 0.01 + 0.1 + 1.0 + 1.0 + 1.0 + 0.01 + 0.1 + 0.001 + 1.0 + 0.05 + 0.001 + 5.0 + 1000.0 + 0.001660538783162726 + 0.0 + 0.01 + 1.0 + 0.01 + 0.1 + 0.0 + 0.0 + 1.0 + 0.0 + 0.01 + 0.0 + 0.01 + + + + + + + degradation_init_l_1 + (((t >= event0.minTime) && (t <= (event0.minTime + 0.9693877551020336))) || ((t >= (event0.minTime + (((event0.maxTime - event0.minTime) / (event0.numTimes + -1.0)) * 1.0))) && (t <= ((event0.minTime + (((event0.maxTime - event0.minTime) / (event0.numTimes + -1.0)) * 1.0)) + 0.9693877551020336))) || ((t >= (event0.minTime + (((event0.maxTime - event0.minTime) / (event0.numTimes + -1.0)) * 2.0))) && (t <= ((event0.minTime + (((event0.maxTime - event0.minTime) / (event0.numTimes + -1.0)) * 2.0)) + 0.9693877551020336))) || ((t >= (event0.minTime + (((event0.maxTime - event0.minTime) / (event0.numTimes + -1.0)) * 3.0))) && (t <= ((event0.minTime + (((event0.maxTime - event0.minTime) / (event0.numTimes + -1.0)) * 3.0)) + 0.9693877551020336))) || ((t >= (event0.minTime + (((event0.maxTime - event0.minTime) / (event0.numTimes + -1.0)) * 4.0))) && (t <= ((event0.minTime + (((event0.maxTime - event0.minTime) / (event0.numTimes + -1.0)) * 4.0)) + 0.9693877551020336))) || ((t >= (event0.minTime + (((event0.maxTime - event0.minTime) / (event0.numTimes + -1.0)) * 5.0))) && (t <= ((event0.minTime + (((event0.maxTime - event0.minTime) / (event0.numTimes + -1.0)) * 5.0)) + 0.9693877551020336))) || ((t >= (event0.minTime + (((event0.maxTime - event0.minTime) / (event0.numTimes + -1.0)) * 6.0))) && (t <= ((event0.minTime + (((event0.maxTime - event0.minTime) / (event0.numTimes + -1.0)) * 6.0)) + 0.9693877551020336))) || ((t >= (event0.minTime + (((event0.maxTime - event0.minTime) / (event0.numTimes + -1.0)) * 7.0))) && (t <= ((event0.minTime + (((event0.maxTime - event0.minTime) / (event0.numTimes + -1.0)) * 7.0)) + 0.9693877551020336))) || ((t >= (event0.minTime + (((event0.maxTime - event0.minTime) / (event0.numTimes + -1.0)) * 8.0))) && (t <= ((event0.minTime + (((event0.maxTime - event0.minTime) / (event0.numTimes + -1.0)) * 8.0)) + 0.9693877551020336))) || ((t >= (event0.minTime + (((event0.maxTime - event0.minTime) / (event0.numTimes + -1.0)) * 9.0))) && (t <= ((event0.minTime + (((event0.maxTime - event0.minTime) / (event0.numTimes + -1.0)) * 9.0)) + 0.9693877551020336))) || ((t >= (event0.minTime + (((event0.maxTime - event0.minTime) / (event0.numTimes + -1.0)) * 10.0))) && (t <= ((event0.minTime + (((event0.maxTime - event0.minTime) / (event0.numTimes + -1.0)) * 10.0)) + 0.9693877551020336))) || ((t >= (event0.minTime + (((event0.maxTime - event0.minTime) / (event0.numTimes + -1.0)) * 11.0))) && (t <= ((event0.minTime + (((event0.maxTime - event0.minTime) / (event0.numTimes + -1.0)) * 11.0)) + 0.9693877551020336))) || ((t >= (event0.minTime + (((event0.maxTime - event0.minTime) / (event0.numTimes + -1.0)) * 12.0))) && (t <= ((event0.minTime + (((event0.maxTime - event0.minTime) / (event0.numTimes + -1.0)) * 12.0)) + 0.9693877551020336))) || ((t >= (event0.minTime + (((event0.maxTime - event0.minTime) / (event0.numTimes + -1.0)) * 13.0))) && (t <= ((event0.minTime + (((event0.maxTime - event0.minTime) / (event0.numTimes + -1.0)) * 13.0)) + 0.9693877551020336))) || ((t >= (event0.minTime + (((event0.maxTime - event0.minTime) / (event0.numTimes + -1.0)) * 14.0))) && (t <= ((event0.minTime + (((event0.maxTime - event0.minTime) / (event0.numTimes + -1.0)) * 14.0)) + 0.9693877551020336))) || ((t >= (event0.minTime + (((event0.maxTime - event0.minTime) / (event0.numTimes + -1.0)) * 15.0))) && (t <= ((event0.minTime + (((event0.maxTime - event0.minTime) / (event0.numTimes + -1.0)) * 15.0)) + 0.9693877551020336))) || ((t >= (event0.minTime + (((event0.maxTime - event0.minTime) / (event0.numTimes + -1.0)) * 16.0))) && (t <= ((event0.minTime + (((event0.maxTime - event0.minTime) / (event0.numTimes + -1.0)) * 16.0)) + 0.9693877551020336))) || ((t >= (event0.minTime + (((event0.maxTime - event0.minTime) / (event0.numTimes + -1.0)) * 17.0))) && (t <= ((event0.minTime + (((event0.maxTime - event0.minTime) / (event0.numTimes + -1.0)) * 17.0)) + 0.9693877551020336))) || ((t >= (event0.minTime + (((event0.maxTime - event0.minTime) / (event0.numTimes + -1.0)) * 18.0))) && (t <= ((event0.minTime + (((event0.maxTime - event0.minTime) / (event0.numTimes + -1.0)) * 18.0)) + 0.9693877551020336))) || ((t >= (event0.minTime + (((event0.maxTime - event0.minTime) / (event0.numTimes + -1.0)) * 19.0))) && (t <= ((event0.minTime + (((event0.maxTime - event0.minTime) / (event0.numTimes + -1.0)) * 19.0)) + 0.9693877551020336))) || ((t >= (event0.minTime + (((event0.maxTime - event0.minTime) / (event0.numTimes + -1.0)) * 20.0))) && (t <= ((event0.minTime + (((event0.maxTime - event0.minTime) / (event0.numTimes + -1.0)) * 20.0)) + 0.9693877551020336))) || ((t >= (event0.minTime + (((event0.maxTime - event0.minTime) / (event0.numTimes + -1.0)) * 21.0))) && (t <= ((event0.minTime + (((event0.maxTime - event0.minTime) / (event0.numTimes + -1.0)) * 21.0)) + 0.9693877551020336))) || ((t >= (event0.minTime + (((event0.maxTime - event0.minTime) / (event0.numTimes + -1.0)) * 22.0))) && (t <= ((event0.minTime + (((event0.maxTime - event0.minTime) / (event0.numTimes + -1.0)) * 22.0)) + 0.9693877551020336))) || ((t >= (event0.minTime + (((event0.maxTime - event0.minTime) / (event0.numTimes + -1.0)) * 23.0))) && (t <= ((event0.minTime + (((event0.maxTime - event0.minTime) / (event0.numTimes + -1.0)) * 23.0)) + 0.9693877551020336))) || ((t >= (event0.minTime + (((event0.maxTime - event0.minTime) / (event0.numTimes + -1.0)) * 24.0))) && (t <= ((event0.minTime + (((event0.maxTime - event0.minTime) / (event0.numTimes + -1.0)) * 24.0)) + 0.9693877551020336))) || ((t >= (event0.minTime + (((event0.maxTime - event0.minTime) / (event0.numTimes + -1.0)) * 25.0))) && (t <= ((event0.minTime + (((event0.maxTime - event0.minTime) / (event0.numTimes + -1.0)) * 25.0)) + 0.9693877551020336))) || ((t >= (event0.minTime + (((event0.maxTime - event0.minTime) / (event0.numTimes + -1.0)) * 26.0))) && (t <= ((event0.minTime + (((event0.maxTime - event0.minTime) / (event0.numTimes + -1.0)) * 26.0)) + 0.9693877551020336))) || ((t >= (event0.minTime + (((event0.maxTime - event0.minTime) / (event0.numTimes + -1.0)) * 27.0))) && (t <= ((event0.minTime + (((event0.maxTime - event0.minTime) / (event0.numTimes + -1.0)) * 27.0)) + 0.9693877551020336))) || ((t >= (event0.minTime + (((event0.maxTime - event0.minTime) / (event0.numTimes + -1.0)) * 28.0))) && (t <= ((event0.minTime + (((event0.maxTime - event0.minTime) / (event0.numTimes + -1.0)) * 28.0)) + 0.9693877551020336))) || ((t >= (event0.minTime + (((event0.maxTime - event0.minTime) / (event0.numTimes + -1.0)) * 29.0))) && (t <= ((event0.minTime + (((event0.maxTime - event0.minTime) / (event0.numTimes + -1.0)) * 29.0)) + 0.9693877551020336))) || ((t >= (event0.minTime + (((event0.maxTime - event0.minTime) / (event0.numTimes + -1.0)) * 30.0))) && (t <= ((event0.minTime + (((event0.maxTime - event0.minTime) / (event0.numTimes + -1.0)) * 30.0)) + 0.9693877551020336))) || ((t >= (event0.minTime + (((event0.maxTime - event0.minTime) / (event0.numTimes + -1.0)) * 31.0))) && (t <= ((event0.minTime + (((event0.maxTime - event0.minTime) / (event0.numTimes + -1.0)) * 31.0)) + 0.9693877551020336))) || ((t >= (event0.minTime + (((event0.maxTime - event0.minTime) / (event0.numTimes + -1.0)) * 32.0))) && (t <= ((event0.minTime + (((event0.maxTime - event0.minTime) / (event0.numTimes + -1.0)) * 32.0)) + 0.9693877551020336))) || ((t >= (event0.minTime + (((event0.maxTime - event0.minTime) / (event0.numTimes + -1.0)) * 33.0))) && (t <= ((event0.minTime + (((event0.maxTime - event0.minTime) / (event0.numTimes + -1.0)) * 33.0)) + 0.9693877551020336))) || ((t >= (event0.minTime + (((event0.maxTime - event0.minTime) / (event0.numTimes + -1.0)) * 34.0))) && (t <= ((event0.minTime + (((event0.maxTime - event0.minTime) / (event0.numTimes + -1.0)) * 34.0)) + 0.9693877551020336))) || ((t >= (event0.minTime + (((event0.maxTime - event0.minTime) / (event0.numTimes + -1.0)) * 35.0))) && (t <= ((event0.minTime + (((event0.maxTime - event0.minTime) / (event0.numTimes + -1.0)) * 35.0)) + 0.9693877551020336))) || ((t >= (event0.minTime + (((event0.maxTime - event0.minTime) / (event0.numTimes + -1.0)) * 36.0))) && (t <= ((event0.minTime + (((event0.maxTime - event0.minTime) / (event0.numTimes + -1.0)) * 36.0)) + 0.9693877551020336))) || ((t >= (event0.minTime + (((event0.maxTime - event0.minTime) / (event0.numTimes + -1.0)) * 37.0))) && (t <= ((event0.minTime + (((event0.maxTime - event0.minTime) / (event0.numTimes + -1.0)) * 37.0)) + 0.9693877551020336))) || ((t >= (event0.minTime + (((event0.maxTime - event0.minTime) / (event0.numTimes + -1.0)) * 38.0))) && (t <= ((event0.minTime + (((event0.maxTime - event0.minTime) / (event0.numTimes + -1.0)) * 38.0)) + 0.9693877551020336))) || ((t >= (event0.minTime + (((event0.maxTime - event0.minTime) / (event0.numTimes + -1.0)) * 39.0))) && (t <= ((event0.minTime + (((event0.maxTime - event0.minTime) / (event0.numTimes + -1.0)) * 39.0)) + 0.9693877551020336))) || ((t >= (event0.minTime + (((event0.maxTime - event0.minTime) / (event0.numTimes + -1.0)) * 40.0))) && (t <= ((event0.minTime + (((event0.maxTime - event0.minTime) / (event0.numTimes + -1.0)) * 40.0)) + 0.9693877551020336))) || ((t >= (event0.minTime + (((event0.maxTime - event0.minTime) / (event0.numTimes + -1.0)) * 41.0))) && (t <= ((event0.minTime + (((event0.maxTime - event0.minTime) / (event0.numTimes + -1.0)) * 41.0)) + 0.9693877551020336))) || ((t >= (event0.minTime + (((event0.maxTime - event0.minTime) / (event0.numTimes + -1.0)) * 42.0))) && (t <= ((event0.minTime + (((event0.maxTime - event0.minTime) / (event0.numTimes + -1.0)) * 42.0)) + 0.9693877551020336))) || ((t >= (event0.minTime + (((event0.maxTime - event0.minTime) / (event0.numTimes + -1.0)) * 43.0))) && (t <= ((event0.minTime + (((event0.maxTime - event0.minTime) / (event0.numTimes + -1.0)) * 43.0)) + 0.9693877551020336))) || ((t >= (event0.minTime + (((event0.maxTime - event0.minTime) / (event0.numTimes + -1.0)) * 44.0))) && (t <= ((event0.minTime + (((event0.maxTime - event0.minTime) / (event0.numTimes + -1.0)) * 44.0)) + 0.9693877551020336))) || ((t >= (event0.minTime + (((event0.maxTime - event0.minTime) / (event0.numTimes + -1.0)) * 45.0))) && (t <= ((event0.minTime + (((event0.maxTime - event0.minTime) / (event0.numTimes + -1.0)) * 45.0)) + 0.9693877551020336))) || ((t >= (event0.minTime + (((event0.maxTime - event0.minTime) / (event0.numTimes + -1.0)) * 46.0))) && (t <= ((event0.minTime + (((event0.maxTime - event0.minTime) / (event0.numTimes + -1.0)) * 46.0)) + 0.9693877551020336))) || ((t >= (event0.minTime + (((event0.maxTime - event0.minTime) / (event0.numTimes + -1.0)) * 47.0))) && (t <= ((event0.minTime + (((event0.maxTime - event0.minTime) / (event0.numTimes + -1.0)) * 47.0)) + 0.9693877551020336))) || ((t >= (event0.minTime + (((event0.maxTime - event0.minTime) / (event0.numTimes + -1.0)) * 48.0))) && (t <= ((event0.minTime + (((event0.maxTime - event0.minTime) / (event0.numTimes + -1.0)) * 48.0)) + 0.9693877551020336))) || ((t >= (event0.minTime + (((event0.maxTime - event0.minTime) / (event0.numTimes + -1.0)) * 49.0))) && (t <= ((event0.minTime + (((event0.maxTime - event0.minTime) / (event0.numTimes + -1.0)) * 49.0)) + 0.9693877551020336)))) + (((t >= event1.minTime) && (t <= (event1.minTime + 1.0204081632653015))) || ((t >= (event1.minTime + (((event1.maxTime - event1.minTime) / (event1.numTimes + -1.0)) * 1.0))) && (t <= ((event1.minTime + (((event1.maxTime - event1.minTime) / (event1.numTimes + -1.0)) * 1.0)) + 1.0204081632653015))) || ((t >= (event1.minTime + (((event1.maxTime - event1.minTime) / (event1.numTimes + -1.0)) * 2.0))) && (t <= ((event1.minTime + (((event1.maxTime - event1.minTime) / (event1.numTimes + -1.0)) * 2.0)) + 1.0204081632653015))) || ((t >= (event1.minTime + (((event1.maxTime - event1.minTime) / (event1.numTimes + -1.0)) * 3.0))) && (t <= ((event1.minTime + (((event1.maxTime - event1.minTime) / (event1.numTimes + -1.0)) * 3.0)) + 1.0204081632653015))) || ((t >= (event1.minTime + (((event1.maxTime - event1.minTime) / (event1.numTimes + -1.0)) * 4.0))) && (t <= ((event1.minTime + (((event1.maxTime - event1.minTime) / (event1.numTimes + -1.0)) * 4.0)) + 1.0204081632653015))) || ((t >= (event1.minTime + (((event1.maxTime - event1.minTime) / (event1.numTimes + -1.0)) * 5.0))) && (t <= ((event1.minTime + (((event1.maxTime - event1.minTime) / (event1.numTimes + -1.0)) * 5.0)) + 1.0204081632653015))) || ((t >= (event1.minTime + (((event1.maxTime - event1.minTime) / (event1.numTimes + -1.0)) * 6.0))) && (t <= ((event1.minTime + (((event1.maxTime - event1.minTime) / (event1.numTimes + -1.0)) * 6.0)) + 1.0204081632653015))) || ((t >= (event1.minTime + (((event1.maxTime - event1.minTime) / (event1.numTimes + -1.0)) * 7.0))) && (t <= ((event1.minTime + (((event1.maxTime - event1.minTime) / (event1.numTimes + -1.0)) * 7.0)) + 1.0204081632653015))) || ((t >= (event1.minTime + (((event1.maxTime - event1.minTime) / (event1.numTimes + -1.0)) * 8.0))) && (t <= ((event1.minTime + (((event1.maxTime - event1.minTime) / (event1.numTimes + -1.0)) * 8.0)) + 1.0204081632653015))) || ((t >= (event1.minTime + (((event1.maxTime - event1.minTime) / (event1.numTimes + -1.0)) * 9.0))) && (t <= ((event1.minTime + (((event1.maxTime - event1.minTime) / (event1.numTimes + -1.0)) * 9.0)) + 1.0204081632653015))) || ((t >= (event1.minTime + (((event1.maxTime - event1.minTime) / (event1.numTimes + -1.0)) * 10.0))) && (t <= ((event1.minTime + (((event1.maxTime - event1.minTime) / (event1.numTimes + -1.0)) * 10.0)) + 1.0204081632653015))) || ((t >= (event1.minTime + (((event1.maxTime - event1.minTime) / (event1.numTimes + -1.0)) * 11.0))) && (t <= ((event1.minTime + (((event1.maxTime - event1.minTime) / (event1.numTimes + -1.0)) * 11.0)) + 1.0204081632653015))) || ((t >= (event1.minTime + (((event1.maxTime - event1.minTime) / (event1.numTimes + -1.0)) * 12.0))) && (t <= ((event1.minTime + (((event1.maxTime - event1.minTime) / (event1.numTimes + -1.0)) * 12.0)) + 1.0204081632653015))) || ((t >= (event1.minTime + (((event1.maxTime - event1.minTime) / (event1.numTimes + -1.0)) * 13.0))) && (t <= ((event1.minTime + (((event1.maxTime - event1.minTime) / (event1.numTimes + -1.0)) * 13.0)) + 1.0204081632653015))) || ((t >= (event1.minTime + (((event1.maxTime - event1.minTime) / (event1.numTimes + -1.0)) * 14.0))) && (t <= ((event1.minTime + (((event1.maxTime - event1.minTime) / (event1.numTimes + -1.0)) * 14.0)) + 1.0204081632653015))) || ((t >= (event1.minTime + (((event1.maxTime - event1.minTime) / (event1.numTimes + -1.0)) * 15.0))) && (t <= ((event1.minTime + (((event1.maxTime - event1.minTime) / (event1.numTimes + -1.0)) * 15.0)) + 1.0204081632653015))) || ((t >= (event1.minTime + (((event1.maxTime - event1.minTime) / (event1.numTimes + -1.0)) * 16.0))) && (t <= ((event1.minTime + (((event1.maxTime - event1.minTime) / (event1.numTimes + -1.0)) * 16.0)) + 1.0204081632653015))) || ((t >= (event1.minTime + (((event1.maxTime - event1.minTime) / (event1.numTimes + -1.0)) * 17.0))) && (t <= ((event1.minTime + (((event1.maxTime - event1.minTime) / (event1.numTimes + -1.0)) * 17.0)) + 1.0204081632653015))) || ((t >= (event1.minTime + (((event1.maxTime - event1.minTime) / (event1.numTimes + -1.0)) * 18.0))) && (t <= ((event1.minTime + (((event1.maxTime - event1.minTime) / (event1.numTimes + -1.0)) * 18.0)) + 1.0204081632653015))) || ((t >= (event1.minTime + (((event1.maxTime - event1.minTime) / (event1.numTimes + -1.0)) * 19.0))) && (t <= ((event1.minTime + (((event1.maxTime - event1.minTime) / (event1.numTimes + -1.0)) * 19.0)) + 1.0204081632653015))) || ((t >= (event1.minTime + (((event1.maxTime - event1.minTime) / (event1.numTimes + -1.0)) * 20.0))) && (t <= ((event1.minTime + (((event1.maxTime - event1.minTime) / (event1.numTimes + -1.0)) * 20.0)) + 1.0204081632653015))) || ((t >= (event1.minTime + (((event1.maxTime - event1.minTime) / (event1.numTimes + -1.0)) * 21.0))) && (t <= ((event1.minTime + (((event1.maxTime - event1.minTime) / (event1.numTimes + -1.0)) * 21.0)) + 1.0204081632653015))) || ((t >= (event1.minTime + (((event1.maxTime - event1.minTime) / (event1.numTimes + -1.0)) * 22.0))) && (t <= ((event1.minTime + (((event1.maxTime - event1.minTime) / (event1.numTimes + -1.0)) * 22.0)) + 1.0204081632653015))) || ((t >= (event1.minTime + (((event1.maxTime - event1.minTime) / (event1.numTimes + -1.0)) * 23.0))) && (t <= ((event1.minTime + (((event1.maxTime - event1.minTime) / (event1.numTimes + -1.0)) * 23.0)) + 1.0204081632653015))) || ((t >= (event1.minTime + (((event1.maxTime - event1.minTime) / (event1.numTimes + -1.0)) * 24.0))) && (t <= ((event1.minTime + (((event1.maxTime - event1.minTime) / (event1.numTimes + -1.0)) * 24.0)) + 1.0204081632653015))) || ((t >= (event1.minTime + (((event1.maxTime - event1.minTime) / (event1.numTimes + -1.0)) * 25.0))) && (t <= ((event1.minTime + (((event1.maxTime - event1.minTime) / (event1.numTimes + -1.0)) * 25.0)) + 1.0204081632653015))) || ((t >= (event1.minTime + (((event1.maxTime - event1.minTime) / (event1.numTimes + -1.0)) * 26.0))) && (t <= ((event1.minTime + (((event1.maxTime - event1.minTime) / (event1.numTimes + -1.0)) * 26.0)) + 1.0204081632653015))) || ((t >= (event1.minTime + (((event1.maxTime - event1.minTime) / (event1.numTimes + -1.0)) * 27.0))) && (t <= ((event1.minTime + (((event1.maxTime - event1.minTime) / (event1.numTimes + -1.0)) * 27.0)) + 1.0204081632653015))) || ((t >= (event1.minTime + (((event1.maxTime - event1.minTime) / (event1.numTimes + -1.0)) * 28.0))) && (t <= ((event1.minTime + (((event1.maxTime - event1.minTime) / (event1.numTimes + -1.0)) * 28.0)) + 1.0204081632653015))) || ((t >= (event1.minTime + (((event1.maxTime - event1.minTime) / (event1.numTimes + -1.0)) * 29.0))) && (t <= ((event1.minTime + (((event1.maxTime - event1.minTime) / (event1.numTimes + -1.0)) * 29.0)) + 1.0204081632653015))) || ((t >= (event1.minTime + (((event1.maxTime - event1.minTime) / (event1.numTimes + -1.0)) * 30.0))) && (t <= ((event1.minTime + (((event1.maxTime - event1.minTime) / (event1.numTimes + -1.0)) * 30.0)) + 1.0204081632653015))) || ((t >= (event1.minTime + (((event1.maxTime - event1.minTime) / (event1.numTimes + -1.0)) * 31.0))) && (t <= ((event1.minTime + (((event1.maxTime - event1.minTime) / (event1.numTimes + -1.0)) * 31.0)) + 1.0204081632653015))) || ((t >= (event1.minTime + (((event1.maxTime - event1.minTime) / (event1.numTimes + -1.0)) * 32.0))) && (t <= ((event1.minTime + (((event1.maxTime - event1.minTime) / (event1.numTimes + -1.0)) * 32.0)) + 1.0204081632653015))) || ((t >= (event1.minTime + (((event1.maxTime - event1.minTime) / (event1.numTimes + -1.0)) * 33.0))) && (t <= ((event1.minTime + (((event1.maxTime - event1.minTime) / (event1.numTimes + -1.0)) * 33.0)) + 1.0204081632653015))) || ((t >= (event1.minTime + (((event1.maxTime - event1.minTime) / (event1.numTimes + -1.0)) * 34.0))) && (t <= ((event1.minTime + (((event1.maxTime - event1.minTime) / (event1.numTimes + -1.0)) * 34.0)) + 1.0204081632653015))) || ((t >= (event1.minTime + (((event1.maxTime - event1.minTime) / (event1.numTimes + -1.0)) * 35.0))) && (t <= ((event1.minTime + (((event1.maxTime - event1.minTime) / (event1.numTimes + -1.0)) * 35.0)) + 1.0204081632653015))) || ((t >= (event1.minTime + (((event1.maxTime - event1.minTime) / (event1.numTimes + -1.0)) * 36.0))) && (t <= ((event1.minTime + (((event1.maxTime - event1.minTime) / (event1.numTimes + -1.0)) * 36.0)) + 1.0204081632653015))) || ((t >= (event1.minTime + (((event1.maxTime - event1.minTime) / (event1.numTimes + -1.0)) * 37.0))) && (t <= ((event1.minTime + (((event1.maxTime - event1.minTime) / (event1.numTimes + -1.0)) * 37.0)) + 1.0204081632653015))) || ((t >= (event1.minTime + (((event1.maxTime - event1.minTime) / (event1.numTimes + -1.0)) * 38.0))) && (t <= ((event1.minTime + (((event1.maxTime - event1.minTime) / (event1.numTimes + -1.0)) * 38.0)) + 1.0204081632653015))) || ((t >= (event1.minTime + (((event1.maxTime - event1.minTime) / (event1.numTimes + -1.0)) * 39.0))) && (t <= ((event1.minTime + (((event1.maxTime - event1.minTime) / (event1.numTimes + -1.0)) * 39.0)) + 1.0204081632653015))) || ((t >= (event1.minTime + (((event1.maxTime - event1.minTime) / (event1.numTimes + -1.0)) * 40.0))) && (t <= ((event1.minTime + (((event1.maxTime - event1.minTime) / (event1.numTimes + -1.0)) * 40.0)) + 1.0204081632653015))) || ((t >= (event1.minTime + (((event1.maxTime - event1.minTime) / (event1.numTimes + -1.0)) * 41.0))) && (t <= ((event1.minTime + (((event1.maxTime - event1.minTime) / (event1.numTimes + -1.0)) * 41.0)) + 1.0204081632653015))) || ((t >= (event1.minTime + (((event1.maxTime - event1.minTime) / (event1.numTimes + -1.0)) * 42.0))) && (t <= ((event1.minTime + (((event1.maxTime - event1.minTime) / (event1.numTimes + -1.0)) * 42.0)) + 1.0204081632653015))) || ((t >= (event1.minTime + (((event1.maxTime - event1.minTime) / (event1.numTimes + -1.0)) * 43.0))) && (t <= ((event1.minTime + (((event1.maxTime - event1.minTime) / (event1.numTimes + -1.0)) * 43.0)) + 1.0204081632653015))) || ((t >= (event1.minTime + (((event1.maxTime - event1.minTime) / (event1.numTimes + -1.0)) * 44.0))) && (t <= ((event1.minTime + (((event1.maxTime - event1.minTime) / (event1.numTimes + -1.0)) * 44.0)) + 1.0204081632653015))) || ((t >= (event1.minTime + (((event1.maxTime - event1.minTime) / (event1.numTimes + -1.0)) * 45.0))) && (t <= ((event1.minTime + (((event1.maxTime - event1.minTime) / (event1.numTimes + -1.0)) * 45.0)) + 1.0204081632653015))) || ((t >= (event1.minTime + (((event1.maxTime - event1.minTime) / (event1.numTimes + -1.0)) * 46.0))) && (t <= ((event1.minTime + (((event1.maxTime - event1.minTime) / (event1.numTimes + -1.0)) * 46.0)) + 1.0204081632653015))) || ((t >= (event1.minTime + (((event1.maxTime - event1.minTime) / (event1.numTimes + -1.0)) * 47.0))) && (t <= ((event1.minTime + (((event1.maxTime - event1.minTime) / (event1.numTimes + -1.0)) * 47.0)) + 1.0204081632653015))) || ((t >= (event1.minTime + (((event1.maxTime - event1.minTime) / (event1.numTimes + -1.0)) * 48.0))) && (t <= ((event1.minTime + (((event1.maxTime - event1.minTime) / (event1.numTimes + -1.0)) * 48.0)) + 1.0204081632653015))) || ((t >= (event1.minTime + (((event1.maxTime - event1.minTime) / (event1.numTimes + -1.0)) * 49.0))) && (t <= ((event1.minTime + (((event1.maxTime - event1.minTime) / (event1.numTimes + -1.0)) * 49.0)) + 1.0204081632653015)))) + ((OCT4_Gene * f * ((A * a1) + (OCT4_SOX2 * a2) + (NANOG * OCT4_SOX2 * a3) + eta1)) / (eta1 + f + (A * b1 * f) + (OCT4_SOX2 * b2 * f) + (NANOG * OCT4_SOX2 * b3 * f))) + (OCT4 * gamma1) + ((eta5 + (e1 * OCT4_SOX2) + (e2 * OCT4_SOX2 * NANOG)) / (1.0 + (eta5 / f) + (f2 * OCT4_SOX2) + (f1 * OCT4_SOX2 * NANOG) + (f3 * p53))) + (NANOG * gamma2) + ((k1c * OCT4 * SOX2) - (k2c * OCT4_SOX2)) + (OCT4_SOX2 * k3c) + ((f * ((A * c1) + (OCT4_SOX2 * c2) + (NANOG * OCT4_SOX2 * c3) + eta3)) / (eta3 + f + (A * d1 * f) + (OCT4_SOX2 * d2 * f) + (NANOG * OCT4_SOX2 * d3 * f))) + (SOX2 * gamma3) + ((f * (eta7 + (OCT4_SOX2 * g1))) / (eta7 + f + (OCT4_SOX2 * f * h1) + (NANOG * OCT4_SOX2 * f * h2))) + (Protein * gamma4) + NANOG_Gene_init_l_1 + OCT4_Gene_init_l_1 + p53_init_l_1 + SOX2_Gene_init_l_1 + targetGene_init_l_1 + + + + + + + + + 0.0 + A_init_l_1 + + + ((LumpedJ_J0 / Size_compartment) - (LumpedJ_J1 / Size_compartment) - (LumpedJ_J4 / Size_compartment)) + OCT4_init_l_1 + + + ( - (LumpedJ_J4 / Size_compartment) + (LumpedJ_J6 / Size_compartment) - (LumpedJ_J7 / Size_compartment)) + SOX2_init_l_1 + + + ((LumpedJ_J2 / Size_compartment) - (LumpedJ_J3 / Size_compartment)) + NANOG_init_l_1 + + + ((LumpedJ_J4 / Size_compartment) - (LumpedJ_J5 / Size_compartment)) + OCT4_SOX2_init_l_1 + + + ((LumpedJ_J8 / Size_compartment) - (LumpedJ_J9 / Size_compartment)) + Protein_init_l_1 + + + + + + + + event1.triggerFunction + (A - 5.0) + + + event0.triggerFunction + (A + 5.0) + + + + + + + + + 1 + + + 0.01 + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tests/unit/resources/SimID_256118677_0_.cvodeInput b/tests/unit/resources/SimID_256118677_0_.cvodeInput new file mode 100644 index 000000000..f5786cddc --- /dev/null +++ b/tests/unit/resources/SimID_256118677_0_.cvodeInput @@ -0,0 +1,24 @@ +# JMS_Paramters +JMS_PARAM_BEGIN +JMS_BROKER vcell.cam.uchc.edu:30163 +JMS_USER clientUser dummy +JMS_QUEUE workerEvent +JMS_TOPIC serviceControl +VCELL_USER ldresche +SIMULATION_KEY 256118677 +JOB_INDEX 0 +JMS_PARAM_END + +SOLVER CVODE +STARTING_TIME 0.0 +ENDING_TIME 10.0 +RELATIVE_TOLERANCE 1.0E-9 +ABSOLUTE_TOLERANCE 1.0E-9 +MAX_TIME_STEP 1.0 +KEEP_EVERY 1 +NUM_EQUATIONS 2 +ODE s0 INIT 1.0; + RATE - ((0.5 * s0) - (0.2 * s1)); +ODE s1 INIT 0.0; + RATE ((0.5 * s0) - (0.2 * s1)); + diff --git a/tests/unit/resources/SimID_256118677_0_.functions b/tests/unit/resources/SimID_256118677_0_.functions new file mode 100644 index 000000000..380a43ce0 --- /dev/null +++ b/tests/unit/resources/SimID_256118677_0_.functions @@ -0,0 +1,6 @@ +##--------------------------------------------- +## /simdata/ldresche/SimID_256118677_0_.functions +##--------------------------------------------- + +Compartment::J_r0; ((0.5 * s0) - (0.2 * s1)); ; Nonspatial_VariableType; false + diff --git a/tests/unit/resources/SimID_256118677_0_.ida.expected b/tests/unit/resources/SimID_256118677_0_.ida.expected new file mode 100644 index 000000000..c0a71f95f --- /dev/null +++ b/tests/unit/resources/SimID_256118677_0_.ida.expected @@ -0,0 +1,159 @@ +t:s0:s1: +0.00000000000000000E+00 1.00000000000000000E+00 0.00000000000000000E+00 +1.02648488190150711E-10 9.99999999948675722E-01 5.13242440913875063E-11 +1.02658753038969712E-06 9.99999486706603657E-01 5.13293396373231300E-07 +1.12914363494047687E-05 9.99994354310467903E-01 5.64568953210529354E-06 +4.48211895830175010E-05 9.99977589784958321E-01 2.24102150417055293E-05 +1.17277047540880841E-04 9.99941363945291539E-01 5.86360547084540949E-05 +2.53272368309885381E-04 9.99873375142890652E-01 1.26624857109333797E-04 +4.96024984269423201E-04 9.99752030703600969E-01 2.47969296398973030E-04 +9.22698163574049712E-04 9.99538800058830001E-01 4.61199941169868684E-04 +1.68044365853622663E-03 9.99160272360433921E-01 8.39727639565850115E-04 +3.18862269929603417E-03 9.98407466672823207E-01 1.59253332717652126E-03 +5.45114110140219790E-03 9.97279622342691741E-01 2.72037765730797233E-03 +7.71365950350836119E-03 9.96153562661734271E-01 3.84643733826544416E-03 +9.97617790561452535E-03 9.95029284922585688E-01 4.97071507741405488E-03 +1.22386963077206895E-02 9.93906786345072457E-01 6.09321365492729479E-03 +1.45012147098268537E-02 9.92786064126930712E-01 7.21393587306905053E-03 +1.73952544462393256E-02 9.91355107543508862E-01 8.64489245649092790E-03 +2.02892941826517993E-02 9.89927047398444282E-01 1.00729526015555741E-02 +2.31833339190642730E-02 9.88501877601865031E-01 1.14981223981348198E-02 +2.60773736554767467E-02 9.87079592114147530E-01 1.29204078858523308E-02 +3.29068688347819438E-02 9.83734614715639721E-01 1.62653852843602023E-02 +3.97363640140871374E-02 9.80405590318767395E-01 1.95944096812325566E-02 +5.02957748456068915E-02 9.75289650084949389E-01 2.47103499150505036E-02 +6.08551856771266456E-02 9.70211385257129355E-01 2.97886147428706068E-02 +7.14145965086463996E-02 9.65170518189519044E-01 3.48294818104809070E-02 +8.19740073401661606E-02 9.60166773545884755E-01 3.98332264541152309E-02 +9.25334181716859216E-02 9.55199878101650190E-01 4.48001218983497956E-02 +1.09712253292456846E-01 9.47197402212381134E-01 5.28025977876188587E-02 +1.26891088413227771E-01 9.39290581299578320E-01 6.07094187004216662E-02 +1.44069923533998695E-01 9.31478272015571096E-01 6.85217279844288346E-02 +1.61248758654769619E-01 9.23759344630696533E-01 7.62406553693034117E-02 +1.89660049003499281E-01 9.11195316277009670E-01 8.88046837229902880E-02 +2.18071339352228943E-01 8.98878691567765453E-01 1.01121308432234533E-01 +2.46482629700958605E-01 8.86804598883420114E-01 1.13195401116579816E-01 +2.74893920049688267E-01 8.74968262536995778E-01 1.25031737463004083E-01 +3.03305210398417957E-01 8.63365000789078185E-01 1.36634999210921676E-01 +3.31716500747147647E-01 8.51990224040054867E-01 1.48009775959944967E-01 +3.60127791095877337E-01 8.40839433068397923E-01 1.59160566931601855E-01 +3.88539081444607026E-01 8.29908217266169590E-01 1.70091782733830132E-01 +4.41138445070370255E-01 8.10235627189399343E-01 1.89764372810600435E-01 +4.77567574856914034E-01 7.97029226508457089E-01 2.02970773491542689E-01 +5.13996704643457814E-01 7.84155337872014080E-01 2.15844662127985643E-01 +5.50425834430001593E-01 7.71605588560225097E-01 2.28394411439774653E-01 +5.86854964216545372E-01 7.59371816557104728E-01 2.40628183442894966E-01 +6.23284094003089151E-01 7.47446066126369102E-01 2.52553933873630565E-01 +6.59713223789632930E-01 7.35820582268969181E-01 2.64179417731030486E-01 +6.96142353576176709E-01 7.24487805053363076E-01 2.75512194946636535E-01 +7.58815310233661888E-01 7.05654481953680968E-01 2.94345518046318699E-01 +8.21488266891147068E-01 6.87629536154125143E-01 3.12370463845874413E-01 +8.84161223548632247E-01 6.70378268923642939E-01 3.29621731076356561E-01 +9.46834180206117426E-01 6.53867470964243247E-01 3.46132529035756198E-01 +1.00950713686360261E+00 6.38065359483856231E-01 3.61934640516143269E-01 +1.07218009352108767E+00 6.22941516449129762E-01 3.77058483550869794E-01 +1.13485305017857274E+00 6.08466829122856878E-01 3.91533170877142678E-01 +1.19752600683605781E+00 5.94613433892461130E-01 4.05386566107538426E-01 +1.26019896349354288E+00 5.81354663050361431E-01 4.18645336949638014E-01 +1.32287192015102795E+00 5.68664993725449097E-01 4.31335006274550459E-01 +1.38554487680851302E+00 5.56519998669646965E-01 4.43480001330352702E-01 +1.44821783346599808E+00 5.44896299070346424E-01 4.55103700929653243E-01 +1.51089079012348315E+00 5.33771519516928472E-01 4.66228480483071195E-01 +1.57356374678096822E+00 5.23124244999401800E-01 4.76875755000597867E-01 +1.63623670343845329E+00 5.12933979731394318E-01 4.87066020268605349E-01 +1.69890966009593836E+00 5.03181107681094364E-01 4.96818892318905247E-01 +1.76158261675342342E+00 4.93846854780748468E-01 5.06153145219251144E-01 +1.82425557341090849E+00 4.84913252781268178E-01 5.15086747218731489E-01 +1.88692853006839356E+00 4.76363104676305715E-01 5.23636895323693952E-01 +1.94960148672587863E+00 4.68179951606793432E-01 5.31820048393206291E-01 +2.01227444338336392E+00 4.60348041167775057E-01 5.39651958832224610E-01 +2.07494740004084921E+00 4.52852297094320178E-01 5.47147702905679489E-01 +2.13762035669833450E+00 4.45678290239669095E-01 5.54321709760330572E-01 +2.20029331335581979E+00 4.38812210792762725E-01 5.61187789207236998E-01 +2.26296627001330508E+00 4.32240841692172695E-01 5.67759158307827083E-01 +2.32563922667079037E+00 4.25951533186333664E-01 5.74048466813666169E-01 +2.38831218332827566E+00 4.19932178485779350E-01 5.80067821514220539E-01 +2.45098513998576095E+00 4.14171190457713889E-01 5.85828809542286000E-01 +2.51365809664324624E+00 4.08657479319653238E-01 5.91342520680346651E-01 +2.57633105330073153E+00 4.03380431291335495E-01 5.96619568708664394E-01 +2.63900400995821682E+00 3.98329888163913515E-01 6.01670111836086319E-01 +2.70167696661570211E+00 3.93496127746071123E-01 6.06503872253928655E-01 +2.76434992327318740E+00 3.88869845148941795E-01 6.11130154851058038E-01 +2.82702287993067269E+00 3.84442134874129537E-01 6.15557865125870296E-01 +2.88969583658815798E+00 3.80204473670731957E-01 6.19795526329267821E-01 +2.95236879324564327E+00 3.76148704128358324E-01 6.23851295871641454E-01 +3.01504174990312857E+00 3.72267018974363595E-01 6.27732981025636239E-01 +3.07771470656061386E+00 3.68551946044992529E-01 6.31448053955007249E-01 +3.14038766321809915E+00 3.64996333901570436E-01 6.35003666098429287E-01 +3.20306061987558444E+00 3.61593338064120084E-01 6.38406661935879582E-01 +3.26573357653306973E+00 3.58336407835898707E-01 6.41663592164100960E-01 +3.32840653319055502E+00 3.55219273693456239E-01 6.44780726306543484E-01 +3.39107948984804031E+00 3.52235935217927731E-01 6.47764064782072047E-01 +3.45375244650552560E+00 3.49380649544342170E-01 6.50619350455657663E-01 +3.51642540316301089E+00 3.46647920306724255E-01 6.53352079693275467E-01 +3.57909835982049618E+00 3.44032487057707448E-01 6.55967512942292386E-01 +3.64177131647798147E+00 3.41529315142283463E-01 6.58470684857716426E-01 +3.70444427313546676E+00 3.39133586006194754E-01 6.60866413993805191E-01 +3.76711722979295205E+00 3.36840687920315074E-01 6.63159312079684926E-01 +3.82979018645043734E+00 3.34646207103166171E-01 6.65353792896833829E-01 +3.89246314310792263E+00 3.32545919224480291E-01 6.67454080775519709E-01 +3.95513609976540792E+00 3.30535781273451845E-01 6.69464218726548155E-01 +4.01780905642289277E+00 3.28611923776025050E-01 6.71388076223975006E-01 +4.08048201308037761E+00 3.26770643346235901E-01 6.73229356653764155E-01 +4.14315496973786246E+00 3.25008395557270624E-01 6.74991604442729431E-01 +4.20582792639534730E+00 3.23321788118517361E-01 6.76678211881482694E-01 +4.26850088305283215E+00 3.21707574345476921E-01 6.78292425654523079E-01 +4.36259663226557493E+00 3.19413195181034726E-01 6.80586804818965274E-01 +4.45669238147831770E+00 3.17265070740067878E-01 6.82734929259932066E-01 +4.55078813069106047E+00 3.15253877871656929E-01 6.84746122128343071E-01 +4.64488387990380325E+00 3.13370887632055728E-01 6.86629112367944217E-01 +4.73897962911654602E+00 3.11607927741266999E-01 6.88392072258733001E-01 +4.83307537832928880E+00 3.09957347043537612E-01 6.90042652956462388E-01 +4.92717112754203157E+00 3.08411982061210621E-01 6.91588017938789434E-01 +5.02126687675477434E+00 3.06965125834067321E-01 6.93034874165932679E-01 +5.11536262596751712E+00 3.05610498904494543E-01 6.94389501095505457E-01 +5.20945837518025989E+00 3.04342222141284424E-01 6.95657777858715631E-01 +5.30355412439300267E+00 3.03154791209282182E-01 6.96845208790717763E-01 +5.39764987360574544E+00 3.02043052635592169E-01 6.97956947364407831E-01 +5.49174562281848821E+00 3.01002181432545757E-01 6.98997818567454243E-01 +5.58584137203123099E+00 3.00027660175653355E-01 6.99972339824346701E-01 +5.67993712124397376E+00 2.99115259411111445E-01 7.00884740588888611E-01 +5.77403287045671654E+00 2.98261019295870233E-01 7.01738980704129878E-01 +5.86812861966945931E+00 2.97461232403345710E-01 7.02538767596654345E-01 +5.96222436888220209E+00 2.96712427631590503E-01 7.03287572368409553E-01 +6.05632011809494486E+00 2.96011355142305865E-01 7.03988644857694190E-01 +6.15041586730768763E+00 2.95354972258738302E-01 7.04645027741261698E-01 +6.24451161652043041E+00 2.94740430259425534E-01 7.05259569740574466E-01 +6.33860736573317318E+00 2.94165062013088063E-01 7.05834937986911881E-01 +6.43270311494591596E+00 2.93626370403025250E-01 7.06373629596974695E-01 +6.52679886415865873E+00 2.93122017490281217E-01 7.06877982509718672E-01 +6.62089461337140150E+00 2.92649814367307026E-01 7.07350185632692807E-01 +6.71499036258414428E+00 2.92207711657821112E-01 7.07792288342178666E-01 +6.80908611179688705E+00 2.91793790622163152E-01 7.08206209377836515E-01 +6.90318186100962983E+00 2.91406254829892186E-01 7.08593745170107536E-01 +6.99727761022237260E+00 2.91043422363364113E-01 7.08956577636635665E-01 +7.09137335943511538E+00 2.90703718518221410E-01 7.09296281481778479E-01 +7.18546910864785815E+00 2.90385668969080357E-01 7.09614331030919643E-01 +7.27956485786060092E+00 2.90087893370859995E-01 7.09912106629139950E-01 +7.37366060707334370E+00 2.89809099368042500E-01 7.10190900631957334E-01 +7.46775635628608647E+00 2.89548076985837011E-01 7.10451923014162823E-01 +7.56185210549882925E+00 2.89303693378861304E-01 7.10696306621138585E-01 +7.65594785471157202E+00 2.89074887914547385E-01 7.10925112085452504E-01 +7.75004360392431479E+00 2.88860667569953156E-01 7.11139332430046678E-01 +7.84413935313705757E+00 2.88660102622012638E-01 7.11339897377987196E-01 +7.93823510234980034E+00 2.88472322612513654E-01 7.11527677387486235E-01 +8.08080669531667084E+00 2.88210359800015581E-01 7.11789640199984253E-01 +8.22337828828354134E+00 2.87973278978804759E-01 7.12026721021195130E-01 +8.36594988125041183E+00 2.87758716623703892E-01 7.12241283376295997E-01 +8.50852147421728233E+00 2.87564533611528172E-01 7.12435466388471661E-01 +8.65109306718415283E+00 2.87388794232750655E-01 7.12611205767249123E-01 +8.79366466015102333E+00 2.87229746815752529E-01 7.12770253184247249E-01 +8.93623625311789382E+00 2.87085806022613133E-01 7.12914193977386645E-01 +9.07880784608476432E+00 2.86955536978731940E-01 7.13044463021267783E-01 +9.22137943905163482E+00 2.86837641067565152E-01 7.13162358932434515E-01 +9.36395103201850532E+00 2.86730943062506516E-01 7.13269056937493207E-01 +9.50652262498537581E+00 2.86634379394257510E-01 7.13365620605742157E-01 +9.64909421795224631E+00 2.86546987502534933E-01 7.13453012497464734E-01 +9.79166581091911681E+00 2.86467896231261898E-01 7.13532103768737769E-01 +9.93423740388598731E+00 2.86396317164659742E-01 7.13603682835339925E-01 +1.00000000000000000E+01 2.86365632344379117E-01 7.13634367655620494E-01 diff --git a/tests/unit/resources/SimID_256118677_0_.log b/tests/unit/resources/SimID_256118677_0_.log new file mode 100644 index 000000000..afdb5aca5 --- /dev/null +++ b/tests/unit/resources/SimID_256118677_0_.log @@ -0,0 +1,4 @@ +IDAData logfile +IDAData text format version 1 +SimID_256118677_0_.ida +KeepMost 1000 diff --git a/IDAWin/tests/smoke/simpleModel_Network_orig.vcml b/tests/unit/resources/simpleModel_Network_orig.vcml similarity index 100% rename from IDAWin/tests/smoke/simpleModel_Network_orig.vcml rename to tests/unit/resources/simpleModel_Network_orig.vcml diff --git a/tests/unit/smoke_test.cpp b/tests/unit/smoke_test.cpp new file mode 100644 index 000000000..ac7485a7f --- /dev/null +++ b/tests/unit/smoke_test.cpp @@ -0,0 +1,112 @@ +// +// Created by Logan Drescher on 11/12/25. +// +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "SundialsSolverInterface.h" + +void compare(const std::filesystem::path& file1, const std::filesystem::path& file2, float tolerance); + + +TEST(SmokeTest, MessagingRequiresJobID) { + #ifndef USE_MESSAGING + return; // no need to test this + #endif + const std::filesystem::path RESOURCE_DIRECTORY{RESOURCE_DIR}; + const std::filesystem::path OUTPUT_TARGET{RESOURCE_DIRECTORY /"SimID_1489333437_0_.ida",}; + const std::array NECESSARY_FILES{ + RESOURCE_DIRECTORY /"SimID_1489333437_0_.cvodeInput", + RESOURCE_DIRECTORY /"SimID_1489333437_0_.ida.expected" + }; + for (const auto& file : NECESSARY_FILES) { + assert(std::filesystem::exists(file)); + } + FILE *outputFile = NULL; + std::ifstream inputFileStream{NECESSARY_FILES[0]}; + if (!inputFileStream.is_open()) { throw std::runtime_error("input file [" + NECESSARY_FILES[0].string() + "] doesn't exit!"); } + + // Open the output file... + if (NULL == (outputFile = fopen(OUTPUT_TARGET.string().c_str(), "w"))) { + throw std::runtime_error("Could not open output file[" + OUTPUT_TARGET.string() + "] for writing."); + } + + EXPECT_THROW(activateSolver(inputFileStream, outputFile, -1), std::runtime_error); + fclose(outputFile); +} + +TEST(SmokeTest, ConfirmExecution) { + #ifdef USE_MESSAGING + const int taskID = 2025, hashID = 256118677; + #else + const int taskID = -1, hashID = 1489333437; + #endif + const std::filesystem::path RESOURCE_DIRECTORY{RESOURCE_DIR}; + const std::filesystem::path OUTPUT_TARGET{RESOURCE_DIRECTORY /std::format("SimID_{}_0_.ida", hashID)}; + const std::array NECESSARY_FILES{ + RESOURCE_DIRECTORY /std::format("SimID_{}_0_.cvodeInput", hashID), + RESOURCE_DIRECTORY /std::format("SimID_{}_0_.ida.expected", hashID) + }; + for (const auto& file : NECESSARY_FILES) { + assert(std::filesystem::exists(file)); + } + FILE *outputFile = NULL; + std::ifstream inputFileStream{NECESSARY_FILES[0]}; + if (!inputFileStream.is_open()) { throw std::runtime_error("input file [" + NECESSARY_FILES[0].string() + "] doesn't exit!"); } + + // Open the output file... + if (NULL == (outputFile = fopen(OUTPUT_TARGET.string().c_str(), "w"))) { + throw std::runtime_error("Could not open output file[" + OUTPUT_TARGET.string() + "] for writing."); + } + + activateSolver(inputFileStream, outputFile, taskID); + fclose(outputFile); + + compare(OUTPUT_TARGET, NECESSARY_FILES[1], 1e-7); +} + +void compare(const std::filesystem::path& file1, const std::filesystem::path& file2, float tolerance) { + std::ifstream fileStream1(file1); + std::ifstream fileStream2(file2); + if (!fileStream1.is_open()) throw std::runtime_error("Could not file: `" + file1.string() + "`"); + if (!fileStream2.is_open()) throw std::runtime_error("Could not file: `" + file2.string() + "`"); + + std::string line1, line2; + bool eof1, eof2; + // we do **NOT** short circuit here, so we use `&` not `&&` + while ( + !((eof1 = !std::getline(fileStream1, line1))) + & + !((eof2 = !std::getline(fileStream2, line2))) + ){ + if (line1 == line2) continue; + double value; + std::istringstream lineStream1{line1}; + std::vector lineValues1; + while (lineStream1 >> value) lineValues1.push_back(value); + std::istringstream lineStream2{line2}; + std::vector lineValues2; + while (lineStream2 >> value) lineValues2.push_back(value); + if (lineValues1.size() != lineValues2.size()) throw std::runtime_error("Length of data does not match"); + + for (int i = 0; i < lineValues2.size(); i++) { + if (lineValues1[i] == lineValues2[i]) continue; + double adjustedTolerance = tolerance * std::max(std::abs(lineValues1[i]), std::abs(lineValues2[i])); + if (std::abs(lineValues1[i] - lineValues2[i]) > adjustedTolerance) + throw std::runtime_error("Values outside tolerance"); + } + } + + // last check; make sure they are both eof! + if (eof1 ^ eof2) { + throw std::runtime_error("Files do not contain same number of lines."); + } +} \ No newline at end of file From 802189242d8a83c6f32d618996655ad1bdaa3b2c Mon Sep 17 00:00:00 2001 From: Logan Drescher Date: Wed, 19 Nov 2025 13:20:00 -0500 Subject: [PATCH 07/34] Made messaging vs non-messaging more flexible --- IDAWin/SundialsSolverInterface.cpp | 46 -------------------- IDAWin/SundialsSolverStandalone.cpp | 2 +- IDAWin/VCellSolverFactory.cpp | 20 +++++---- conan-profiles/CI-CD/MacOS-AMD64_profile.txt | 2 +- tests/unit/smoke_test.cpp | 25 +++++------ 5 files changed, 24 insertions(+), 71 deletions(-) diff --git a/IDAWin/SundialsSolverInterface.cpp b/IDAWin/SundialsSolverInterface.cpp index ae3b8fdeb..e52cc2953 100644 --- a/IDAWin/SundialsSolverInterface.cpp +++ b/IDAWin/SundialsSolverInterface.cpp @@ -15,52 +15,6 @@ #define IDA_SOLVER "IDA" void activateSolver(std::ifstream& inputFileStream, FILE* outputFile, int taskID) { - // std::string solver; - // - // while (!inputFileStream.eof()) { // Note break statement if "SOLVER" encountered - // std::string nextToken; - // inputFileStream >> nextToken; - // if (nextToken.empty()) continue; - // if (nextToken[0] == '#') getline(inputFileStream, nextToken); - // else if (nextToken == "JMS_PARAM_BEGIN") { - // loadJMSInfo(inputFileStream, taskID); - // #ifdef USE_MESSAGING - // SimulationMessaging::getInstVar()->start(); // start the thread - // #endif - // } else if (nextToken == "SOLVER") { - // inputFileStream >> solver; - // break; - // } - // } - // #ifdef USE_MESSAGING - // // should only happen during testing for solver compiled with messaging but run locally. - // if (SimulationMessaging::getInstVar() == nullptr) { SimulationMessaging::create(); } - // #endif - // - // if (solver.empty()) { throw "Solver not defined "; } - // VCellSundialsSolver *vss = nullptr; - // - // if (solver == IDA_SOLVER) { - // vss = new VCellIDASolver(); - // } else if (solver == CVODE_SOLVER) { - // vss = new VCellCVodeSolver(); - // } else { - // std::stringstream ss; - // ss << "Solver " << solver << " not defined!"; - // throw ss.str(); - // } - // - // vss->solve(nullptr, true, outputFile, VCellSundialsSolver::checkStopRequested); - // delete vss; - - // Check if we have messaging and a taskID of -1; if so; fail now. - - #ifdef USE_MESSAGING - if (taskID < 0) { - throw std::runtime_error("task id of value: " + std::to_string(taskID) + " not acceptable when this library is built with messaging"); - } - #endif - VCellSolver* targetSolver = VCellSolverFactory::produceVCellSolver(inputFileStream, taskID); targetSolver->solve(nullptr, true, outputFile, VCellSundialsSolver::checkStopRequested); } diff --git a/IDAWin/SundialsSolverStandalone.cpp b/IDAWin/SundialsSolverStandalone.cpp index fb6da561b..0f7d28811 100644 --- a/IDAWin/SundialsSolverStandalone.cpp +++ b/IDAWin/SundialsSolverStandalone.cpp @@ -36,7 +36,7 @@ int parseAndRunWithArgParse(int argc, char *argv[]) { argumentParser.add_argument("input").help("path to directory with input files.").store_into(inputFilePath); argumentParser.add_argument("output").help("path to directory for output files.").store_into(outputFilePath); #ifdef USE_MESSAGING - argumentParser.add_argument("-tid").help("path to solver to run.").store_into(taskID); + argumentParser.add_argument("-tid").help("id of the job").store_into(taskID); #endif try { diff --git a/IDAWin/VCellSolverFactory.cpp b/IDAWin/VCellSolverFactory.cpp index 558454e4b..de756b035 100644 --- a/IDAWin/VCellSolverFactory.cpp +++ b/IDAWin/VCellSolverFactory.cpp @@ -103,6 +103,12 @@ VCellSolverInputBreakdown VCellSolverFactory::parseInputFile(std::ifstream& inpu else throw VCell::Exception("Unexpected token \"" + nextToken + "\" in the input file!"); } + + + #ifdef USE_MESSAGING + // Since messaging assumes we have a job requiring messaging, we should initialize a "default" messaging handler + if (NULL == SimulationMessaging::getInstVar()) SimulationMessaging::create(); + #endif return inputBreakdown; } @@ -191,7 +197,7 @@ static void readEvents(std::istream &inputStream, VCellSolverInputBreakdown& inp //eventComponents.eventAssignments.emplace_back(varIndex, assignmentExpression); // should try this in the future, more descriptive } break; - } else { throw VCell::Exception("Unexpected token \"" + token + "\" in the input file!"); } + } else { throw VCell::Exception("Unexpected event token \"" + token + "\" in the input file!"); } } inputBreakdown.eventSettings.EVENTS.push_back(std::move(eventComponents)); } @@ -421,13 +427,10 @@ static void collectSteadyStateTerms(std::ifstream& inputFileStream, VCellSolverI static void loadJMSInfo(std::istream &ifsInput, int taskID) { #ifndef USE_MESSAGING - return; // Only useful for messaging; let's not waste time! - #else - - if (taskID < 0) { - SimulationMessaging::create(); - return; // No need to do any parsing - } + // We'll still parse the section, as we can still execute the simulation; we'll just toss the values! + std::cerr << "WARNING: Input file expects messaging capabilities; this build does not support JMS messaging!" << std::endl; + #endif + std::string broker; std::string smqUserName; std::string password; @@ -459,6 +462,7 @@ static void loadJMSInfo(std::istream &ifsInput, int taskID) { else if (nextToken == "JOB_INDEX") ifsInput >> jobIndex; } + #ifdef USE_MESSAGING SimulationMessaging::create(broker.c_str(), smqUserName.c_str(), password.c_str(), qName.c_str(), topicName.c_str(), vCellUsername.c_str(), simKey, jobIndex, taskID); diff --git a/conan-profiles/CI-CD/MacOS-AMD64_profile.txt b/conan-profiles/CI-CD/MacOS-AMD64_profile.txt index 2d4556f59..bc4007fdf 100644 --- a/conan-profiles/CI-CD/MacOS-AMD64_profile.txt +++ b/conan-profiles/CI-CD/MacOS-AMD64_profile.txt @@ -4,5 +4,5 @@ build_type=Release compiler=apple-clang compiler.cppstd=20 compiler.libcxx=libc++ -compiler.version=13 +compiler.version=17 os=Macos \ No newline at end of file diff --git a/tests/unit/smoke_test.cpp b/tests/unit/smoke_test.cpp index ac7485a7f..6a6f66893 100644 --- a/tests/unit/smoke_test.cpp +++ b/tests/unit/smoke_test.cpp @@ -16,16 +16,13 @@ void compare(const std::filesystem::path& file1, const std::filesystem::path& file2, float tolerance); - -TEST(SmokeTest, MessagingRequiresJobID) { - #ifndef USE_MESSAGING - return; // no need to test this - #endif +TEST(SmokeTest, UserProvidesFilesWithoutJMS) { + constexpr int taskID = -1, hashID = 1489333437; const std::filesystem::path RESOURCE_DIRECTORY{RESOURCE_DIR}; - const std::filesystem::path OUTPUT_TARGET{RESOURCE_DIRECTORY /"SimID_1489333437_0_.ida",}; + const std::filesystem::path OUTPUT_TARGET{RESOURCE_DIRECTORY /std::format("SimID_{}_0_.ida", hashID)}; const std::array NECESSARY_FILES{ - RESOURCE_DIRECTORY /"SimID_1489333437_0_.cvodeInput", - RESOURCE_DIRECTORY /"SimID_1489333437_0_.ida.expected" + RESOURCE_DIRECTORY /std::format("SimID_{}_0_.cvodeInput", hashID), + RESOURCE_DIRECTORY /std::format("SimID_{}_0_.ida.expected", hashID) }; for (const auto& file : NECESSARY_FILES) { assert(std::filesystem::exists(file)); @@ -39,16 +36,14 @@ TEST(SmokeTest, MessagingRequiresJobID) { throw std::runtime_error("Could not open output file[" + OUTPUT_TARGET.string() + "] for writing."); } - EXPECT_THROW(activateSolver(inputFileStream, outputFile, -1), std::runtime_error); + activateSolver(inputFileStream, outputFile, taskID); fclose(outputFile); + + compare(OUTPUT_TARGET, NECESSARY_FILES[1], 1e-7); } -TEST(SmokeTest, ConfirmExecution) { - #ifdef USE_MESSAGING - const int taskID = 2025, hashID = 256118677; - #else - const int taskID = -1, hashID = 1489333437; - #endif +TEST(SmokeTest, UserProvidesFilesWithJMS) { + constexpr int taskID = 2025, hashID = 256118677; const std::filesystem::path RESOURCE_DIRECTORY{RESOURCE_DIR}; const std::filesystem::path OUTPUT_TARGET{RESOURCE_DIRECTORY /std::format("SimID_{}_0_.ida", hashID)}; const std::array NECESSARY_FILES{ From ed75ea25eb4566649579f9ad7633633aa73c9d2c Mon Sep 17 00:00:00 2001 From: Logan Drescher Date: Tue, 9 Dec 2025 13:12:55 -0500 Subject: [PATCH 08/34] Create separate functionality classes --- VCellMessaging/CMakeLists.txt | 17 +- .../include/VCELL/CurlProxyClasses.h | 90 ++++++++ VCellMessaging/include/VCELL/JobEventStatus.h | 29 +++ .../include/VCELL/MessageEventManager.h | 62 ++++++ VCellMessaging/include/VCELL/WorkerEvent.h | 51 +++++ VCellMessaging/src/CurlProxyClasses.cpp | 207 ++++++++++++++++++ VCellMessaging/src/JobEventStatus.cpp | 23 ++ VCellMessaging/src/MessageEventManager.cpp | 102 +++++++++ 8 files changed, 577 insertions(+), 4 deletions(-) create mode 100644 VCellMessaging/include/VCELL/CurlProxyClasses.h create mode 100644 VCellMessaging/include/VCELL/JobEventStatus.h create mode 100644 VCellMessaging/include/VCELL/MessageEventManager.h create mode 100644 VCellMessaging/include/VCELL/WorkerEvent.h create mode 100644 VCellMessaging/src/CurlProxyClasses.cpp create mode 100644 VCellMessaging/src/JobEventStatus.cpp create mode 100644 VCellMessaging/src/MessageEventManager.cpp diff --git a/VCellMessaging/CMakeLists.txt b/VCellMessaging/CMakeLists.txt index 35772800b..633117474 100644 --- a/VCellMessaging/CMakeLists.txt +++ b/VCellMessaging/CMakeLists.txt @@ -6,11 +6,18 @@ list(APPEND SOURCES GitDescribe.h) set(HEADER_FILES include/VCELL/SimulationMessaging.h include/VCELL/GitDescribe.h + include/VCELL/MessageEventManager.h + include/VCELL/CurlProxyClasses.h + include/VCELL/JobEventStatus.h + include/VCELL/WorkerEvent.h ) set(SRC_FILES src/SimulationMessaging.cpp - "${CMAKE_CURRENT_BINARY_DIR}/GitDescribe.cpp") + "${CMAKE_CURRENT_BINARY_DIR}/GitDescribe.cpp" + src/MessageEventManager.cpp + src/CurlProxyClasses.cpp + src/JobEventStatus.cpp) include_directories(include) @@ -20,9 +27,11 @@ target_include_directories(vcellmessaging INTERFACE $ $ # /include ) -if (${OPTION_TARGET_MESSAGING}) - message(STATUS "CURL_LIBRARIES = '${CURL_LIBRARIES}'") - message(STATUS "CURL_INCLUDE_DIR = '${CURL_INCLUDE_DIR}'") +if (OPTION_TARGET_MESSAGING) + if (OPTION_EXTRA_CONFIG_INFO) + message(STATUS "CURL_LIBRARIES = '${CURL_LIBRARIES}'") + message(STATUS "CURL_INCLUDE_DIR = '${CURL_INCLUDE_DIR}'") + endif () target_link_libraries(vcellmessaging ${CURL_LIBRARIES} Threads::Threads) target_compile_definitions(vcellmessaging PUBLIC USE_MESSAGING=1) diff --git a/VCellMessaging/include/VCELL/CurlProxyClasses.h b/VCellMessaging/include/VCELL/CurlProxyClasses.h new file mode 100644 index 000000000..54de41acc --- /dev/null +++ b/VCellMessaging/include/VCELL/CurlProxyClasses.h @@ -0,0 +1,90 @@ +// +// Created by Logan Drescher on 11/25/25. +// +#ifndef VCELL_ODE_NUMERICS_CURLPROXY_H +#define VCELL_ODE_NUMERICS_CURLPROXY_H +#include +#include + +#include "VCELL/WorkerEvent.h" + + +class AbstractCurlProxy { + public: + virtual ~AbstractCurlProxy() =default; + + virtual void sendStatus(WorkerEvent* event) = 0; + virtual void keepAlive() = 0; +}; + +class NullCurlProxy final : public AbstractCurlProxy { + public: + NullCurlProxy() =default; + ~NullCurlProxy()override =default; + void sendStatus(WorkerEvent* event) override {} + void keepAlive() override {} +}; + +#ifdef USE_MESSAGING +class CurlProxy final : public AbstractCurlProxy { + public: + CurlProxy(long simKey, int taskID, int jobIndex, const std::string& vcusername, const std::string& broker, int ttlLow, int ttlHigh); + ~CurlProxy() override; + + void sendStatus(WorkerEvent* event) override; + void keepAlive() override; + private: + static const char* TIMETOLIVE_PROPERTY; + static const char* DELIVERYMODE_PROPERTY; + static const char* DELIVERYMODE_PERSISTENT_VALUE; + static const char* DELIVERYMODE_NONPERSISTENT_VALUE; + static const char* PRIORITY_PROPERTY; + static const char* PRIORITY_DEFAULT_VALUE; + + static const char* MESSAGE_TYPE_PROPERTY; + static const char* MESSAGE_TYPE_WORKEREVENT_VALUE; + + static const char* USERNAME_PROPERTY; + static const char* HOSTNAME_PROPERTY; + static const char* SIMKEY_PROPERTY; + static const char* TASKID_PROPERTY; + static const char* JOBINDEX_PROPERTY; + static const char* WORKEREVENT_STATUS; + static const char* WORKEREVENT_PROGRESS; + static const char* WORKEREVENT_TIMEPOINT; + static const char* WORKEREVENT_STATUSMSG; + + int TTL_LOW_PRIORITY; + int TTL_HIGH_PRIORITY; + + + long simKey; + int taskID; + int jobIndex; + std::string hostname; + std::string broker; + std::string vcusername; + time_t lastTimeEventWasSent; +}; + + + +// This comment is transplanted from when this logic was a part of Simulation Messaging; originally written by Jim Schaff +// Documentation for the ActiveMQ restful API is missing, must see source code +// +// https://github.com/apache/activemq/blob/master/activemq-web/src/main/java/org/apache/activemq/web/MessageServlet.java +// https://github.com/apache/activemq/blob/master/activemq-web/src/main/java/org/apache/activemq/web/MessageServletSupport.java +// +// currently, the "web" api seems to use the same credentials as the "web console" ... defaults to admin:admin. +// TODO: pass in credentials, and protect them better (consider HTTPS). +// +/* + PROPERTIES="JMSDeliveryMode=persistent&JMSTimeToLive=3000" + PROPERTIES="${PROPERTIES}&SimKey=12446271133&JobIndex=0&TaskID=0&UserName=schaff" + PROPERTIES="${PROPERTIES}&MessageType=WorkerEvent&WorkerEvent_Status=1001&WorkerEvent_StatusMsg=Running" + PROPERTIES="${PROPERTIES}&WorkerEvent_TimePoint=2.0&WorkerEvent_Progress=0.4&HostName=localhost" + curl -XPOST "http://admin:admin@`hostname`:8165/api/message/workerEvent?type=queue&${PROPERTIES}" +*/ +#endif //USE_MESSAGING + +#endif //VCELL_ODE_NUMERICS_CURLPROXY_H \ No newline at end of file diff --git a/VCellMessaging/include/VCELL/JobEventStatus.h b/VCellMessaging/include/VCELL/JobEventStatus.h new file mode 100644 index 000000000..5c6648290 --- /dev/null +++ b/VCellMessaging/include/VCELL/JobEventStatus.h @@ -0,0 +1,29 @@ +// +// Created by Logan Drescher on 11/25/25. +// + +#ifndef VCELL_ODE_NUMERICS_JOBEVENTSTATUS_H +#define VCELL_ODE_NUMERICS_JOBEVENTSTATUS_H +#include +// enum JobEventStatus { +// JOB_STARTING = 999, +// JOB_DATA = 1000, +// JOB_PROGRESS = 1001, +// JOB_COMPLETED = 1003, +// JOB_FAILURE = 1002, +// JOB_ALIVE = 1004, +// }; + +namespace JobEvent { + enum Status { + JOB_STARTING = 999, + JOB_DATA = 1000, + JOB_PROGRESS = 1001, + JOB_COMPLETED = 1003, + JOB_FAILURE = 1002, + JOB_ALIVE = 1004, + }; + std::string toString(Status status); +} + +#endif //VCELL_ODE_NUMERICS_JOBEVENTSTATUS_H \ No newline at end of file diff --git a/VCellMessaging/include/VCELL/MessageEventManager.h b/VCellMessaging/include/VCELL/MessageEventManager.h new file mode 100644 index 000000000..d9d425ea9 --- /dev/null +++ b/VCellMessaging/include/VCELL/MessageEventManager.h @@ -0,0 +1,62 @@ +// +// Created by Logan Drescher on 11/24/25. +// +#ifndef VCELL_ODE_NUMERICS_MESSAGEEVENTQUEUE_H +#define VCELL_ODE_NUMERICS_MESSAGEEVENTQUEUE_H + +#include +#include +#include +#include +#include +#include "WorkerEvent.h" +/* + * We want to avoid threads being idle, while processing updates + * Components: + * 1) An "active" boolean (locked by a mutex) that indicates whether the queue is being processed. + * 1a) We must ensure that all relevant information a thread would use to ensure it's done, is always locked by the mutex. + * 2) A jthread dedicated to processing the queue when it has items, and sleeping when it does not + * 3) A condition variable used to ensure the jthread only runs when it needs to. + * + * LOCK ORDERING + * We are using two mutexes that could conflict with each other + * 1) Mutex for whether the worker is active + * 2) Mutex for access to the event queue + * + * There is a potential deadlock if the worker gets ownership of his isActive mutex, + * while another thread owns the event queue mutex. This is because before the worker decides to "clock out", + * they would check if they have work in the queue. Since the queue is owned, deadlock. + * *****ALWAYS LOCK THE IS_WORKER_ACTIVE MUTEX BEFORE THE QUEUE MUTEX******* + * + * The inverse is possible if the code is changed so that: the worker owns the queue the entire time its doing work, + * until it's empty. At the time of this warning, this is not the case. + */ +class MessageEventManager { + public: + explicit MessageEventManager(std::function sendUpdateFunction); + virtual ~MessageEventManager(); + void enqueue(JobEvent::Status status, double progress, double timepoint, const char *eventMessage); + void enqueue(JobEvent::Status status, double progress, double timepoint); + void enqueue(JobEvent::Status status, const char *eventMessage); + void requestStopAndWaitForIt(); + bool stopWasCalled(); + + private: + void processQueue(); + void processEvent(WorkerEvent* event); + void enqueue(WorkerEvent*); + + std::mutex timeClockMutex; + + bool stopRequested; + std::condition_variable requestedStopForeman; + std::mutex stopRequestedMutex; + std::queue eventQueue; + std::mutex queuetex; + + std::jthread eventQueueProcessingWorkerThread; + std::condition_variable needMessageProcessingForeman; + std::function sendUpdateFunction; +}; + +#endif //VCELL_ODE_NUMERICS_MESSAGEEVENTQUEUE_H \ No newline at end of file diff --git a/VCellMessaging/include/VCELL/WorkerEvent.h b/VCellMessaging/include/VCELL/WorkerEvent.h new file mode 100644 index 000000000..13e799090 --- /dev/null +++ b/VCellMessaging/include/VCELL/WorkerEvent.h @@ -0,0 +1,51 @@ +// +// Created by Logan Drescher on 11/25/25. +// + +#ifndef VCELL_ODE_NUMERICS_WORKEREVENT_H +#define VCELL_ODE_NUMERICS_WORKEREVENT_H +#include + +#include "JobEventStatus.h" + +struct WorkerEvent { + JobEvent::Status status; + double progress; + double timepoint; + std::string eventMessage; + + WorkerEvent(const WorkerEvent* aWorkerEvent) { + status = aWorkerEvent->status; + progress = aWorkerEvent->progress; + timepoint = aWorkerEvent->timepoint; + eventMessage = aWorkerEvent->eventMessage; + } + + WorkerEvent(JobEvent::Status status, double progress, double timepoint, const char *eventMessage) + :status(status), + progress(progress), + timepoint(timepoint), + eventMessage(eventMessage) {} + + WorkerEvent(JobEvent::Status status, double progress, double timepoint) + :status(status), + progress(progress), + timepoint(timepoint){} + + + WorkerEvent(JobEvent::Status arg_status, const char* eventMessage) + :status(arg_status), + progress(0), + timepoint(0), + eventMessage(eventMessage) {} + + bool equals(const WorkerEvent* aWorkerEvent) const { + return nullptr != aWorkerEvent + && this->status == aWorkerEvent->status + && this->progress == aWorkerEvent->progress + && this->timepoint == aWorkerEvent->timepoint + && this->eventMessage == aWorkerEvent->eventMessage; + } +}; + +#endif //VCELL_ODE_NUMERICS_WORKEREVENT_H \ No newline at end of file diff --git a/VCellMessaging/src/CurlProxyClasses.cpp b/VCellMessaging/src/CurlProxyClasses.cpp new file mode 100644 index 000000000..78f07f2ae --- /dev/null +++ b/VCellMessaging/src/CurlProxyClasses.cpp @@ -0,0 +1,207 @@ +// +// Created by Logan Drescher on 11/25/25. +// +#include "VCELL/CurlProxyClasses.h" +#include +#include + + +std::string trim(const std::string& str) { + std::string trimmedStr{str}; + auto whitespaceLambda = [](const unsigned char ch) { return !std::isspace(ch); }; + const auto endOfLeftmostWhitespace = std::ranges::find_if(trimmedStr, whitespaceLambda); + trimmedStr.erase(trimmedStr.begin(), endOfLeftmostWhitespace); + // need to one at a time, because positions will shift on `string::erase` + const auto startOfRightmostWhitespace = std::ranges::find_if(trimmedStr.rbegin(), trimmedStr.rend(), whitespaceLambda).base(); + trimmedStr.erase(startOfRightmostWhitespace, trimmedStr.end()); + return trimmedStr; +} + +#ifdef USE_MESSAGING +#include +#include +#include + +#include "VCELL/JobEventStatus.h" +#include "VCELL/SimulationMessaging.h" + +const char* CurlProxy::TIMETOLIVE_PROPERTY = "JMSTimeToLive"; +const char* CurlProxy::DELIVERYMODE_PROPERTY = "JMSDeliveryMode"; +const char* CurlProxy::DELIVERYMODE_PERSISTENT_VALUE = "persistent"; +const char* CurlProxy::DELIVERYMODE_NONPERSISTENT_VALUE = "nonpersistent"; +const char* CurlProxy::PRIORITY_PROPERTY = "JMSPriority"; +const char* CurlProxy::PRIORITY_DEFAULT_VALUE = "5"; + +const char* CurlProxy::MESSAGE_TYPE_PROPERTY = "MessageType"; +const char* CurlProxy::MESSAGE_TYPE_WORKEREVENT_VALUE = "WorkerEvent"; + +const char* CurlProxy::USERNAME_PROPERTY = "UserName"; +const char* CurlProxy::HOSTNAME_PROPERTY = "HostName"; +const char* CurlProxy::SIMKEY_PROPERTY = "SimKey"; +const char* CurlProxy::TASKID_PROPERTY = "TaskID"; +const char* CurlProxy::JOBINDEX_PROPERTY = "JobIndex"; +const char* CurlProxy::WORKEREVENT_STATUS = "WorkerEvent_Status"; +const char* CurlProxy::WORKEREVENT_PROGRESS = "WorkerEvent_Progress"; +const char* CurlProxy::WORKEREVENT_TIMEPOINT = "WorkerEvent_TimePoint"; +const char* CurlProxy::WORKEREVENT_STATUSMSG = "WorkerEvent_StatusMsg"; + +CurlProxy::CurlProxy(const long simKey, const int taskID, const int jobIndex, const std::string& vcusername, const std::string& broker, const int ttlLow, const int ttlHigh) { + curl_global_init(CURL_GLOBAL_ALL); + this->TTL_LOW_PRIORITY = ttlLow; + this->TTL_HIGH_PRIORITY = ttlHigh; + + this->simKey = simKey; + this->taskID = taskID; + this->jobIndex = jobIndex; + this->broker = broker; + this->vcusername = vcusername; + + std::array buf{}; + if (-1 == gethostname(buf.data(), buf.size())) + throw std::runtime_error("Unable to retrieve hostname"); + this->hostname = std::string(buf.data()); + + this->lastTimeEventWasSent = 0; + time(&this->lastTimeEventWasSent); // Update with current time +} + +CurlProxy::~CurlProxy() { + curl_global_cleanup(); +} + + +void CurlProxy::sendStatus(WorkerEvent* event) { + CURL *curlHandler = curl_easy_init(); + if (!curlHandler) { + #ifdef USE_MESSAGING + throw std::runtime_error("Unable to initialize CURL"); + #else + return; + #endif + } + std::stringstream ss_url; + ss_url << "http://" << "admin" << ":" << "admin" << "@" << this->broker << "/api/message/workerEvent?type=queue&"; + switch (event->status) { + case JobEvent::JOB_DATA: + case JobEvent::JOB_PROGRESS: + ss_url << PRIORITY_PROPERTY << "=" << PRIORITY_DEFAULT_VALUE << "&"; + ss_url << TIMETOLIVE_PROPERTY << "=" << this->TTL_LOW_PRIORITY << "&"; + ss_url << DELIVERYMODE_PROPERTY << "=" << DELIVERYMODE_NONPERSISTENT_VALUE << "&"; + break; + case JobEvent::JOB_STARTING: + case JobEvent::JOB_COMPLETED: + case JobEvent::JOB_FAILURE: + ss_url << PRIORITY_PROPERTY << "=" << PRIORITY_DEFAULT_VALUE << "&"; + ss_url << TIMETOLIVE_PROPERTY << "=" << this->TTL_HIGH_PRIORITY << "&"; + ss_url << DELIVERYMODE_PROPERTY << "=" << DELIVERYMODE_PERSISTENT_VALUE << "&"; + break; + case JobEvent::JOB_ALIVE: + throw std::runtime_error(std::format("SimulationMessaging::sendStatus: `{}` is not recognized",JobEvent::toString(event->status))); + } + + ss_url << MESSAGE_TYPE_PROPERTY << "=" << MESSAGE_TYPE_WORKEREVENT_VALUE << "&"; + ss_url << USERNAME_PROPERTY << "=" << this->vcusername << "&"; + ss_url << HOSTNAME_PROPERTY << "=" << this->hostname << "&"; + ss_url << SIMKEY_PROPERTY << "=" << this->simKey << "&"; + ss_url << TASKID_PROPERTY << "=" << this->taskID << "&"; + ss_url << JOBINDEX_PROPERTY << "=" << this->jobIndex << "&"; + + ss_url << WORKEREVENT_STATUS << "=" << event->status << "&"; + + std::string revisedMsg{event->eventMessage}; + if (!revisedMsg.empty()) { + revisedMsg = trim(revisedMsg); + //status message is only 2048 chars long in database, need null char + if (revisedMsg.size() > 2047) revisedMsg.resize(2047); + + for (char & i : revisedMsg) { + switch (i) { + // these characters are not valid both in database and in messages as a property + case '\r': + case '\n': + case '\'': + case '\"': + i = ' '; + break; + default: continue; + } + } + } + + if (!revisedMsg.empty()) { + if(char *output = curl_easy_escape(curlHandler, revisedMsg.c_str(), static_cast(revisedMsg.size()))) { + ss_url << WORKEREVENT_STATUSMSG << "=" << output << "&"; + curl_free(output); + } + } + + ss_url << WORKEREVENT_PROGRESS << "=" << event->progress << "&"; + ss_url << WORKEREVENT_TIMEPOINT << "=" << event->timepoint; + + std::string s_url = ss_url.str(); + const char* messaging_http_url = s_url.c_str(); + curl_easy_setopt(curlHandler, CURLOPT_URL, messaging_http_url); + + std::cout << "curl -XPOST " << messaging_http_url << std::endl; + + // print message to stdout + std::cout << "!!!SimulationMessaging::sendStatus [" << this->simKey << ":" << JobEvent::toString(event->status); + if (!revisedMsg.empty()) { + std::cout << ":" << revisedMsg; + } else { + std::cout << ":" << event->progress << ":" << event->timepoint; + } + std::cout << "]" << std::endl; + + + + // std::stringstream ss_body; + // ss_body << "empty message body"; // one way to force a POST verb with libcurl, probably better ways. + // std::string s_body = ss_body.str(); + // const char* postfields = s_body.c_str(); + curl_easy_setopt(curlHandler, CURLOPT_POSTFIELDS, ""); + + + // Perform the request, res will get the return code + // Check for errors + if(CURLcode res = curl_easy_perform(curlHandler); res != CURLE_OK) + fprintf(stderr, "curl_easy_perform() failed: %s\n", curl_easy_strerror(res)); + + // always cleanup + curl_easy_cleanup(curlHandler); +} + +void CurlProxy::keepAlive() { + CURL *curlHandler = curl_easy_init(); + if (!curlHandler) { + #ifdef USE_MESSAGING + throw std::runtime_error("Unable to initialize CURL"); + #else + return; + #endif + } + // First set the URL that is about to receive our POST. This URL can + // just as well be a https:// URL if that is what should receive the + // data. + curl_easy_setopt(curlHandler, CURLOPT_URL, "http://localhost:6161/message/workerevent?readTimeout=20000&type=queue"); + + + // Now specify the POST data + std::stringstream ss; + ss << WORKEREVENT_STATUS << "=" << JobEvent::JOB_ALIVE; + + + curl_easy_setopt(curlHandler, CURLOPT_POSTFIELDS, ss.str().c_str()); + + // Perform the request, res will get the return code + + // Check for errors + //TODO: Potentially move this to outer scope using exceptions; we shouldn't be quiet here + if (const CURLcode res = curl_easy_perform(curlHandler); res != CURLE_OK) + std::cerr << "curl_easy_perform() failed: " << curl_easy_strerror(res) << std::endl; + + curl_easy_cleanup(curlHandler); // always cleanup + time(&this->lastTimeEventWasSent); +} + +#endif \ No newline at end of file diff --git a/VCellMessaging/src/JobEventStatus.cpp b/VCellMessaging/src/JobEventStatus.cpp new file mode 100644 index 000000000..22281f510 --- /dev/null +++ b/VCellMessaging/src/JobEventStatus.cpp @@ -0,0 +1,23 @@ +// +// Created by Logan Drescher on 11/25/25. +// +#include + +std::string JobEvent::toString(const Status status) { + switch (status) { + case JOB_ALIVE: + return "JOB_ALIVE"; + case JOB_FAILURE: + return "JOB_DEAD"; + case JOB_PROGRESS: + return "JOB_PROGRESS"; + case JOB_COMPLETED: + return "JOB_COMPLETED"; + case JOB_DATA: + return "JOB_DATA"; + case JOB_STARTING: + return "JOB_STARTING"; + default: + throw std::runtime_error("Unknown status type"); + } +} diff --git a/VCellMessaging/src/MessageEventManager.cpp b/VCellMessaging/src/MessageEventManager.cpp new file mode 100644 index 000000000..4bc775328 --- /dev/null +++ b/VCellMessaging/src/MessageEventManager.cpp @@ -0,0 +1,102 @@ +// +// Created by Logan Drescher on 11/24/25. +// +#include +#include +#include + +MessageEventManager::MessageEventManager(std::function sendUpdateFunction): + stopRequested{false}, + sendUpdateFunction{std::move(sendUpdateFunction)}, + eventQueueProcessingWorkerThread([this]() {this->processQueue();}) // Should auto-start thread +{ + +} + +MessageEventManager::~MessageEventManager() { + if (!this->stopRequested) this->requestStopAndWaitForIt(); +} + +void MessageEventManager::enqueue(const JobEvent::Status status, const double progress, const double timepoint, const char *eventMessage) { + this->enqueue(new WorkerEvent(status, progress, timepoint, eventMessage)); +} + +void MessageEventManager::enqueue(const JobEvent::Status status, const double progress, const double timepoint) { + this->enqueue(status, progress, timepoint, ""); +} + +void MessageEventManager::enqueue(const JobEvent::Status status, const char *eventMessage) { + this->enqueue(status, 0, 0, eventMessage); +} + +void MessageEventManager::requestStopAndWaitForIt() { + std::unique_lock stopRequestedLock{this->stopRequestedMutex}; + this->stopRequested = true; + this->needMessageProcessingForeman.notify_all(); + this->requestedStopForeman.wait(stopRequestedLock, [this]()->bool{return this->eventQueue.empty();}); +} + +bool MessageEventManager::stopWasCalled() { + std::unique_lock stopRequestedLock{this->stopRequestedMutex}; + return this->stopRequested; + +} + +/////////////////////////////////////////////////// +/// Private Methods /// +/////////////////////////////////////////////////// + +void MessageEventManager::processQueue() { + while (true) { + WorkerEvent* event; + {// START Clock-out Scope // + // The order of the locks is important! See Header File + std::unique_lock checkIfTheresWorkLock{this->timeClockMutex}; // This is to prevent loop processing while `enqueue` is being called + std::unique_lock shouldBeActiveLock(this->queuetex); + if (this->eventQueue.empty()) { + { // START stop-requested Scope + std::unique_lock stopRequestedLock{this->stopRequestedMutex}; + if (this->stopRequested) { + this->requestedStopForeman.notify_all(); + return; // We're all done; since this should be done on a jthread, this should also trigger a join. + } + } // END stop-requested Scope + checkIfTheresWorkLock.unlock(); // Need to allow enqueuing, can't do that with this locked, and we can't rescope but unique_lock order matters above! + // The worker can stop working, and "get some sleep" + // Wait for the worker to be "prodded" (via `needMessagingForeman.notify_one()`), AND the event queue is not empty + // Note that this `wait()` call, by design, unlocks the queue mutex, until it is "prodded". + this->needMessageProcessingForeman.wait(shouldBeActiveLock); + if (this->eventQueue.empty()) continue; // Probably means we need to check if stop was requested again + } + event = this->eventQueue.front(); + this->eventQueue.pop(); + }// END Clock-out Scope // + + // Process Event + this->processEvent(event); + // Remember to delete the event! We need the memory back! + delete event; + } +} + +void MessageEventManager::processEvent(WorkerEvent* event) { + this->sendUpdateFunction(event); +} + +void MessageEventManager::enqueue(WorkerEvent* event) { + // The order of the locks is important! See Header File + std::lock_guard workerIsActiveLock{this->timeClockMutex}; // Need to lock to prevent worker from "clocking out" while we set this up + std::lock_guard stopRequestedLock{this->stopRequestedMutex}; // We scope this lock to the whole function; "last in the door" policy + if (this->stopRequested) { + std::cerr << "A new event was added to the messaging queue, but this queue has had `stop` requested!" << std::endl; + delete event; + return; // note: on this return function scope ends, and thus so too does out function-scoped locks + } + + {// START Emplace Scope // + std::lock_guard queueLock{this->queuetex}; + this->eventQueue.emplace(event); + }// END Emplace Scope // + + this->needMessageProcessingForeman.notify_one(); // Nudges one sleeping worker awake. If the worker is awake...this does nothing; as it should. +} \ No newline at end of file From 100e3024e7b4ab180c8dd8539521547da4af8245 Mon Sep 17 00:00:00 2001 From: Logan Drescher Date: Tue, 9 Dec 2025 13:13:49 -0500 Subject: [PATCH 09/34] Wire in new classes into workflow; add tests --- IDAWin/SundialsSolverInterface.cpp | 37 +- IDAWin/SundialsSolverStandalone.cpp | 45 +- IDAWin/VCellCVodeSolver.cpp | 2 +- IDAWin/VCellIDASolver.cpp | 2 +- IDAWin/VCellSolverFactory.cpp | 8 +- IDAWin/VCellSundialsSolver.cpp | 6 +- .../include/VCELL/SimulationMessaging.h | 291 ++---- VCellMessaging/src/SimulationMessaging.cpp | 864 ++++-------------- tests/CMakeLists.txt | 3 +- tests/unit/message_processing_test.cpp | 66 ++ 10 files changed, 367 insertions(+), 957 deletions(-) create mode 100644 tests/unit/message_processing_test.cpp diff --git a/IDAWin/SundialsSolverInterface.cpp b/IDAWin/SundialsSolverInterface.cpp index e52cc2953..7aadee250 100644 --- a/IDAWin/SundialsSolverInterface.cpp +++ b/IDAWin/SundialsSolverInterface.cpp @@ -10,12 +10,45 @@ #include #include #include +#include +#include "StoppedByUserException.h" #define CVODE_SOLVER "CVODE" #define IDA_SOLVER "IDA" +void errExit(int returnCode, const std::string &errorMsg); + void activateSolver(std::ifstream& inputFileStream, FILE* outputFile, int taskID) { - VCellSolver* targetSolver = VCellSolverFactory::produceVCellSolver(inputFileStream, taskID); - targetSolver->solve(nullptr, true, outputFile, VCellSundialsSolver::checkStopRequested); + try { + VCellSolver* targetSolver = VCellSolverFactory::produceVCellSolver(inputFileStream, taskID); + targetSolver->solve(nullptr, true, outputFile, VCellSundialsSolver::checkStopRequested); + } catch (const char *ex) { + errExit(-1, ex); + } catch (std::string &ex) { + errExit(-2, ex); + } catch (StoppedByUserException&) { + errExit(0, "Execution Stopped By User"); + } catch (VCell::Exception &ex) { + errExit(-3, ex.getMessage()); + } catch (const std::exception& err) { + errExit(-4, err.what()); + } catch (...) { + errExit(-5, "unknown error"); + } + + // cleanup + if (SimulationMessaging::getInstVar() != nullptr) { + SimulationMessaging::getInstVar()->waitUntilFinished(); + delete SimulationMessaging::getInstVar(); + } +} + +void errExit(int returnCode, const std::string &errorMsg) { + #ifdef USE_MESSAGING + if (returnCode && SimulationMessaging::getInstVar() && !SimulationMessaging::getInstVar()->isStopRequested()) { + SimulationMessaging::getInstVar()->setWorkerEvent(JobEvent::JOB_FAILURE, errorMsg.c_str()); + } + #endif + std::cerr << errorMsg << std::endl; } diff --git a/IDAWin/SundialsSolverStandalone.cpp b/IDAWin/SundialsSolverStandalone.cpp index 0f7d28811..ed5f9bbf6 100644 --- a/IDAWin/SundialsSolverStandalone.cpp +++ b/IDAWin/SundialsSolverStandalone.cpp @@ -1,8 +1,7 @@ // Messaging -#ifdef USE_MESSAGING + #include #include -#endif // Standard Includes #include #include @@ -18,7 +17,6 @@ #define IDA_SOLVER "IDA" int parseAndRunWithArgParse(int argc, char *argv[]); -void errExit(int returnCode, std::string &errorMsg); int main(int argc, char *argv[]) { std::cout << std::setprecision(20); @@ -58,46 +56,15 @@ int parseAndRunWithArgParse(int argc, char *argv[]) { throw std::runtime_error("Could not open output file[" + outputFilePath + "] for writing."); } activateSolver(inputFileStream, outputFile, taskID); - - } catch (const char *ex) { - errMsg += ex; - returnCode = -1; - } catch (std::string &ex) { - errMsg += ex; - returnCode = -1; - } catch (StoppedByUserException&) { - returnCode = 0; // stopped by user; - } catch (VCell::Exception &ex) { - errMsg += ex.getMessage(); - returnCode = -1; - } catch (const std::exception& err) { - errMsg += err.what(); - returnCode = -1; + } catch (const std::runtime_error& err) { + std::cerr << err.what() << std::endl; + returnCode = 5; } catch (...) { - errMsg += "unknown error"; - returnCode = -1; + std::cerr << "Unknown exception thrown." << std::endl; + returnCode = 255; } if (outputFile != NULL) { fclose(outputFile); } if (inputFileStream.is_open()) { inputFileStream.close(); } - errExit(returnCode, errMsg); return returnCode; } - -void errExit(int returnCode, std::string &errorMsg) { - #ifdef USE_MESSAGING - if (returnCode != 0) { - if (SimulationMessaging::getInstVar() != nullptr && !SimulationMessaging::getInstVar()->isStopRequested()) { - SimulationMessaging::getInstVar()->setWorkerEvent(new WorkerEvent(JOB_FAILURE, errorMsg.c_str())); - } - } - #endif - - if (returnCode != 0) std::cerr << errorMsg << std::endl; - #ifdef USE_MESSAGING - else if (SimulationMessaging::getInstVar() != nullptr) { - SimulationMessaging::getInstVar()->waitUntilFinished(); - delete SimulationMessaging::getInstVar(); - } - #endif -} diff --git a/IDAWin/VCellCVodeSolver.cpp b/IDAWin/VCellCVodeSolver.cpp index e37b7d418..1e09dd970 100644 --- a/IDAWin/VCellCVodeSolver.cpp +++ b/IDAWin/VCellCVodeSolver.cpp @@ -501,7 +501,7 @@ void VCellCVodeSolver::cvodeSolve(bool bPrintProgress, FILE* outputFile, void (* } } #ifdef USE_MESSAGING - SimulationMessaging::getInstVar()->setWorkerEvent(new WorkerEvent(JOB_COMPLETED, 1, Time)); + SimulationMessaging::getInstVar()->setWorkerEvent(JobEvent::JOB_COMPLETED, 1, Time); #endif } diff --git a/IDAWin/VCellIDASolver.cpp b/IDAWin/VCellIDASolver.cpp index 4d45a1a7f..70c70bf2b 100644 --- a/IDAWin/VCellIDASolver.cpp +++ b/IDAWin/VCellIDASolver.cpp @@ -718,7 +718,7 @@ void VCellIDASolver::idaSolve(bool bPrintProgress, FILE* outputFile, void (*chec } } #ifdef USE_MESSAGING - SimulationMessaging::getInstVar()->setWorkerEvent(new WorkerEvent(JOB_COMPLETED, 1, Time)); + SimulationMessaging::getInstVar()->setWorkerEvent(JobEvent::JOB_COMPLETED, 1, Time); #endif } diff --git a/IDAWin/VCellSolverFactory.cpp b/IDAWin/VCellSolverFactory.cpp index de756b035..3fe83577b 100644 --- a/IDAWin/VCellSolverFactory.cpp +++ b/IDAWin/VCellSolverFactory.cpp @@ -60,9 +60,9 @@ VCellSolverInputBreakdown VCellSolverFactory::parseInputFile(std::ifstream& inpu inputBreakdown.solverType = determineSolverType(solverName); } else if (nextToken == "JMS_PARAM_BEGIN") { loadJMSInfo(inputFileStream, taskID); - #ifdef USE_MESSAGING - SimulationMessaging::getInstVar()->start(); // start the thread - #endif + // #ifdef USE_MESSAGING + // SimulationMessaging::getInstVar()->start(); // start the thread + // #endif } else if (nextToken == "STARTING_TIME") { inputFileStream >> inputBreakdown.timeCourseSettings.STARTING_TIME; } else if (nextToken == "ENDING_TIME") { inputFileStream >> inputBreakdown.timeCourseSettings.ENDING_TIME; } @@ -430,7 +430,7 @@ static void loadJMSInfo(std::istream &ifsInput, int taskID) { // We'll still parse the section, as we can still execute the simulation; we'll just toss the values! std::cerr << "WARNING: Input file expects messaging capabilities; this build does not support JMS messaging!" << std::endl; #endif - + std::string broker; std::string smqUserName; std::string password; diff --git a/IDAWin/VCellSundialsSolver.cpp b/IDAWin/VCellSundialsSolver.cpp index 99286c686..3ff290601 100644 --- a/IDAWin/VCellSundialsSolver.cpp +++ b/IDAWin/VCellSundialsSolver.cpp @@ -122,7 +122,7 @@ void VCellSundialsSolver::printProgress(double currTime, double &lastPercentile, if (currTime == STARTING_TIME) { // print 0% #ifdef USE_MESSAGING - SimulationMessaging::getInstVar()->setWorkerEvent(new WorkerEvent(JOB_PROGRESS, lastPercentile, currTime)); + SimulationMessaging::getInstVar()->setWorkerEvent(JobEvent::JOB_PROGRESS, lastPercentile, currTime); #else printf("[[[progress:%lg%%]]]", lastPercentile*100.0); fflush(stdout); @@ -144,8 +144,8 @@ void VCellSundialsSolver::printProgress(double currTime, double &lastPercentile, if (lastPercentile != newPercentile) { #ifdef USE_MESSAGING SimulationMessaging::getInstVar()-> - setWorkerEvent(new WorkerEvent(JOB_PROGRESS, newPercentile, currTime)); - SimulationMessaging::getInstVar()->setWorkerEvent(new WorkerEvent(JOB_DATA, newPercentile, currTime)); + setWorkerEvent(JobEvent::JOB_PROGRESS, newPercentile, currTime); + SimulationMessaging::getInstVar()->setWorkerEvent(JobEvent::JOB_DATA, newPercentile, currTime); #else printf("[[[progress:%lg%%]]]", newPercentile*100.0); printf("[[[data:%lg]]]", currTime); diff --git a/VCellMessaging/include/VCELL/SimulationMessaging.h b/VCellMessaging/include/VCELL/SimulationMessaging.h index e5562b21a..d12be5dfa 100644 --- a/VCellMessaging/include/VCELL/SimulationMessaging.h +++ b/VCellMessaging/include/VCELL/SimulationMessaging.h @@ -1,234 +1,107 @@ #ifndef _SIMULATIONMESSAGING_H_ #define _SIMULATIONMESSAGING_H_ +#include #include -#ifdef USE_MESSAGING -#include -#include -#include - -#endif #include - -#if (defined(_WIN32) || defined(_WIN64) ) -#include -#else -#include -#include -#include #include -#endif +#include "CurlProxyClasses.h" +#include "MessageEventManager.h" +#include "WorkerEvent.h" #ifdef USE_MESSAGING -#if (!defined(_WIN32) && !defined(_WIN64) ) #include -#else -#include -#endif -static const int ONE_SECOND = 1000; -static const int ONE_MINUTE = 60 * ONE_SECOND; -static const int DEFAULT_TTL_HIGH = 10 * ONE_MINUTE; -static const int DEFAULT_TTL_LOW = ONE_MINUTE; +static constexpr int ONE_SECOND = 1000; +static constexpr int ONE_MINUTE = 60 * ONE_SECOND; +static constexpr int DEFAULT_TTL_HIGH = 10 * ONE_MINUTE; +static constexpr int DEFAULT_TTL_LOW = ONE_MINUTE; #endif -static const int WORKEREVENT_OUTPUT_MODE_STDOUT = 0; -static const int WORKEREVENT_OUTPUT_MODE_MESSAGING = 1; - -static const int JOB_STARTING = 999; -static const int JOB_DATA = 1000; -static const int JOB_PROGRESS = 1001; -static const int JOB_FAILURE = 1002; -static const int JOB_COMPLETED = 1003; -static const int JOB_WORKER_ALIVE = 1004; - -static const int DEFAULT_PRIORITY = 0; //range 0-127, the bigger number the higher priority -static const int Message_DEFAULT_PRIORITY = 0; - -struct WorkerEvent { - int status; - double progress; - double timepoint; - char* eventMessage; - - WorkerEvent(const WorkerEvent* aWorkerEvent) { - status = aWorkerEvent->status; - progress = aWorkerEvent->progress; - timepoint = aWorkerEvent->timepoint; - eventMessage = copy(aWorkerEvent->eventMessage); - } - - WorkerEvent(int arg_status, double arg_progress, double arg_timepoint, const char *arg_eventMessage) - :status(arg_status), - progress(arg_progress), - timepoint(arg_timepoint), - eventMessage(copy(arg_eventMessage)) {} - - WorkerEvent(int arg_status, double arg_progress, double arg_timepoint) - :status(arg_status), - progress(arg_progress), - timepoint(arg_timepoint), - eventMessage(0) {} - - - WorkerEvent(int arg_status, const char* arg_eventMessage) - :status(arg_status), - progress(0), - timepoint(0), - eventMessage(copy(arg_eventMessage)) {} -private: - static char *copy(const char *in) { - if (in != 0) { - size_t len = strlen(in) + 1; - char * c = new char[len + 1]; - memset(c, 0, len); - strcpy(c, in); - return c; - } - return 0; - } -public: - - bool equals(WorkerEvent* aWorkerEvent) { - if (status != aWorkerEvent->status || progress != aWorkerEvent->progress || timepoint != aWorkerEvent->timepoint) { - return false; - } - if (eventMessage != NULL && aWorkerEvent->eventMessage != NULL && strcmp(eventMessage, aWorkerEvent->eventMessage) != 0) { - return false; - } - - if (eventMessage != NULL && aWorkerEvent->eventMessage == NULL - || eventMessage == NULL && aWorkerEvent->eventMessage != NULL) { - return false; - } +enum WorkerEventOutputMode { + // ***Align values to bits*** + WORKEREVENT_OUTPUT_MODE_STDOUT = 1, + WORKEREVENT_OUTPUT_MODE_MESSAGING = 2, + // = 4 + // = 8 + WORKEREVENT_OUTPUT_MODE_ALL = 3 // This should be 2^n -1, where n is number of bits - return true; - } }; -class SimulationMessaging -{ -public: - virtual ~SimulationMessaging() throw(); - static SimulationMessaging* create(); - static SimulationMessaging* getInstVar(); - void start(); - void setWorkerEvent(WorkerEvent* workerEvent); +static constexpr int DEFAULT_PRIORITY = 0; //range 0-127, the bigger number the higher priority +static constexpr int Message_DEFAULT_PRIORITY = 0; - bool isStopRequested() { - return bStopRequested; - } - int getTaskID() { -#ifdef USE_MESSAGING - return m_taskID; -#else - return -1; -#endif - } +//TODO: Re-write so that there is a messaging handler abstract class with two children: stdOut and CUrl +class SimulationMessaging { + public: + virtual ~SimulationMessaging() noexcept; + static void initialize(); + static SimulationMessaging* create(); + static SimulationMessaging* getInstVar(); + void setWorkerEvent(JobEvent::Status status, const char *eventMessage); + void setWorkerEvent(JobEvent::Status status, double progress, double timepoint); + void setWorkerEvent(JobEvent::Status status, double progress, double timepoint, const char *eventMessage); - int getJobIndex() { -#ifdef USE_MESSAGING - return m_jobIndex; -#else - return 0; -#endif - } -#ifdef USE_MESSAGING - static SimulationMessaging* create(const char* broker, const char* smqusername, const char* passwd, const char* qname, const char* tname, - const char* vcusername, int simKey, int jobIndex, int taskID, int ttl_low=DEFAULT_TTL_LOW, int ttl_high=DEFAULT_TTL_HIGH); - void waitUntilFinished(); - friend void* startMessagingThread(void* param); -#endif + bool isStopRequested() { + return this->eventHandler.stopWasCalled(); + } -private: - SimulationMessaging(); - static SimulationMessaging *m_inst; - std::vector events; - int workerEventOutputMode; - - void sendStatus(); - bool bStopRequested; - /** - * is this a critical message type? - */ - bool criticalDelivery(const WorkerEvent & event) { - switch (event.status) { - case JOB_DATA: - case JOB_PROGRESS: - return false; - default: - return true; + [[nodiscard]] int getTaskID() const { // default = -1 + return this->taskID; } - } -#ifdef USE_MESSAGING - bool bStarted; - - SimulationMessaging(const char* broker, const char* smqusername, const char* passwd, const char* qname, const char*tname, const char* vcusername, int simKey, int jobIndex, int taskID, int ttl_low=DEFAULT_TTL_LOW, int ttl_high=DEFAULT_TTL_HIGH); - - void keepAlive(); - static char* trim(char* str); - void setupConnection (); //synchronized - void cleanup(); - - char *m_broker; - char *m_smqusername; - char *m_password; - char *m_qname; - char* m_tname; - char* m_vcusername; - int m_simKey; - int m_taskID; - int m_jobIndex; - - char* m_tListener; - - char m_hostname[256]; - int m_ttl_lowPriority; - int m_ttl_highPriority; - time_t lastSentEventTime; - - const char* getStatusString(int status); - - bool lockMessaging(); - void unlockMessaging(); - void delay(int duration); - - bool lockWorkerEvent(bool bTry=false); - void unlockWorkerEvent(); - struct WorkerEventLocker { - WorkerEventLocker(SimulationMessaging &sm_, bool bTry = false) - :sm(sm_), - locked( sm.lockWorkerEvent(false)) { - } - ~WorkerEventLocker( ) { - sm.unlockWorkerEvent(); - } - SimulationMessaging &sm; - const bool locked; - }; - -#ifdef WIN32 - CRITICAL_SECTION lockForMessaging; - CRITICAL_SECTION lockForWorkerEvent; - HANDLE hNewWorkerEvent; - HANDLE hMessagingThreadEndEvent; -#else // UNIX - pthread_t newWorkerEventThread; - pthread_mutex_t mutex_messaging; - pthread_mutex_t mutex_workerEvent; - pthread_mutex_t mutex_cond_workerEvent; - pthread_cond_t cond_workerEvent; - bool bNewWorkerEvent; -#endif + [[nodiscard]] int getJobIndex() const { // default = 0 + return m_jobIndex; + } + void waitUntilFinished(); + #ifdef USE_MESSAGING + static SimulationMessaging* create(const char* broker, const char* smqusername, const char* passwd, const char* qname, const char* tname, + const char* vcusername, int simKey, int jobIndex, int taskID, int ttl_low=DEFAULT_TTL_LOW, int ttl_high=DEFAULT_TTL_HIGH); + friend void* startMessagingThread(void* param); + #endif + + private: + // Statics + static void sendStdOutStatus(WorkerEvent*); + static bool criticalDelivery(const WorkerEvent & event) { + // is this a critical message type? + switch (event.status) { + case JobEvent::JOB_DATA: + case JobEvent::JOB_PROGRESS: + return false; + default: + return true; + } + } + static SimulationMessaging *m_inst; + + // Members + static bool isInitialized; + + SimulationMessaging(); + #ifdef USE_MESSAGING + SimulationMessaging(const char* broker, const char* vcusername, int simKey, int jobIndex, int taskID, int ttl_low=DEFAULT_TTL_LOW, int ttl_high=DEFAULT_TTL_HIGH); + #endif + + void sendStatus(WorkerEvent*); + void keepAlive(); + std::vector events; + MessageEventManager eventHandler; + AbstractCurlProxy *curlHandler; + WorkerEventOutputMode workerEventOutputMode; + + int taskID; + int m_jobIndex; + time_t lastSentEventTime; + + pthread_t newWorkerEventThread; + pthread_mutex_t mutex_messaging; + pthread_mutex_t mutex_workerEvent; + pthread_mutex_t mutex_cond_workerEvent; + pthread_cond_t cond_workerEvent; + bool bNewWorkerEvent; -#else - //NO MESSAGING compile compatibility variant - struct WorkerEventLocker { - WorkerEventLocker(SimulationMessaging &, bool bTry = false) {} - }; -#endif }; #endif // _SIMULATIONMESSAGING_H_ diff --git a/VCellMessaging/src/SimulationMessaging.cpp b/VCellMessaging/src/SimulationMessaging.cpp index e127d2b85..47d5c729e 100644 --- a/VCellMessaging/src/SimulationMessaging.cpp +++ b/VCellMessaging/src/SimulationMessaging.cpp @@ -1,749 +1,219 @@ #include #include #include +#include #include -#include +#include +#include -#include -#ifdef USE_MESSAGING -#if ( !defined(_WIN32) && !defined(_WIN64) ) // UNIX -#include -#include -#endif - -static const char* TIMETOLIVE_PROPERTY = "JMSTimeToLive"; -static const char* DELIVERYMODE_PROPERTY = "JMSDeliveryMode"; -static const char* DELIVERYMODE_PERSISTENT_VALUE = "persistent"; -static const char* DELIVERYMODE_NONPERSISTENT_VALUE = "nonpersistent"; -static const char* PRIORITY_PROPERTY = "JMSPriority"; -static const char* PRIORITY_DEFAULT_VALUE = "5"; -static const char* MESSAGE_TYPE_PROPERTY = "MessageType"; -static const char* MESSAGE_TYPE_WORKEREVENT_VALUE = "WorkerEvent"; - -static const char* USERNAME_PROPERTY = "UserName"; -static const char* HOSTNAME_PROPERTY = "HostName"; -static const char* SIMKEY_PROPERTY = "SimKey"; -static const char* TASKID_PROPERTY = "TaskID"; -static const char* JOBINDEX_PROPERTY = "JobIndex"; -static const char* WORKEREVENT_STATUS = "WorkerEvent_Status"; -static const char* WORKEREVENT_PROGRESS = "WorkerEvent_Progress"; -static const char* WORKEREVENT_TIMEPOINT = "WorkerEvent_TimePoint"; -static const char* WORKEREVENT_STATUSMSG = "WorkerEvent_StatusMsg"; static const double WORKEREVENT_MESSAGE_MIN_TIME_SECONDS = 15.0; +bool SimulationMessaging::isInitialized = false; -#endif SimulationMessaging *SimulationMessaging::m_inst = NULL; -SimulationMessaging::SimulationMessaging(){ - this->bStopRequested = false; -#ifdef USE_MESSAGING - this->m_taskID = -1; - this->bStarted = false; -#endif +SimulationMessaging::SimulationMessaging(): + eventHandler(std::bind(&SimulationMessaging::sendStatus, this, std::placeholders::_1)), + taskID{-1}, + m_jobIndex{-1} +{ + this->taskID = -1; this->workerEventOutputMode = WORKEREVENT_OUTPUT_MODE_STDOUT; -} - -#ifdef USE_MESSAGING -SimulationMessaging::SimulationMessaging(const char* broker, const char* smqusername, const char* passwd, const char*qname, - const char* tname, const char* vcusername, int simKey, int jobIndex, int taskID, int ttl_low, int ttl_high){ - m_broker = const_cast(broker); - m_smqusername = const_cast( smqusername ); - m_password = const_cast(passwd ); - m_qname = const_cast( qname ); - m_tname = const_cast( tname ); - - m_vcusername = const_cast( vcusername ); - m_simKey = simKey; - m_jobIndex = jobIndex; - m_taskID = taskID; - - m_ttl_lowPriority = ttl_low; - m_ttl_highPriority = ttl_high; - - curl_global_init(CURL_GLOBAL_ALL); - - { - memset(m_hostname, 0, 256); - - // get hostname -#if ( defined(_WIN32) || defined(_WIN64) ) - WSADATA wsaData; - WORD wVersionRequested = MAKEWORD( 2, 2 ); - WSAStartup( wVersionRequested, &wsaData ); -#endif - gethostname(m_hostname, 256); - //cout) << "hostname is " << m_hostname << endl; - } - - time(&lastSentEventTime); - - workerEventOutputMode = WORKEREVENT_OUTPUT_MODE_MESSAGING; - -#if ( defined(_WIN32) || defined(_WIN64) ) - InitializeCriticalSection(&lockForMessaging); - InitializeCriticalSection(&lockForWorkerEvent); -#else // UNIX - pthread_mutex_init(&mutex_messaging, NULL); - pthread_mutex_init(&mutex_workerEvent, NULL); - pthread_mutex_init(&mutex_cond_workerEvent, NULL); - pthread_cond_init(&cond_workerEvent, NULL); - bNewWorkerEvent = false; -#endif + this->curlHandler = new NullCurlProxy(); - bStarted = false; -} -#endif - -namespace { - void cleanupWorkerEvent(WorkerEvent *we) { - delete we; - } } -SimulationMessaging::~SimulationMessaging() throw() -{ - if (workerEventOutputMode == WORKEREVENT_OUTPUT_MODE_STDOUT) { - return; - } - std::for_each(events.begin(), events.end( ),cleanupWorkerEvent); - /* - if (workerEvent != NULL) { - delete workerEvent; - workerEvent = NULL; - } - */ #ifdef USE_MESSAGING - cleanup(); -#if ( defined(_WIN32) || defined(_WIN64) ) - DeleteCriticalSection(&lockForMessaging); - DeleteCriticalSection(&lockForWorkerEvent); -#else // UNIX - pthread_mutex_destroy(&mutex_messaging); - pthread_mutex_destroy(&mutex_workerEvent); - pthread_mutex_destroy(&mutex_cond_workerEvent); - pthread_cond_destroy(&cond_workerEvent); -#endif - curl_global_cleanup(); -#endif -} - -SimulationMessaging* SimulationMessaging::getInstVar() { - return m_inst; -} - -SimulationMessaging* SimulationMessaging::create() +SimulationMessaging::SimulationMessaging(const char* broker, const char* vcusername, int simKey, int jobIndex, int taskID, int ttl_low, int ttl_high): + eventHandler(std::bind(&SimulationMessaging::sendStatus, this, std::placeholders::_1)), + workerEventOutputMode{WORKEREVENT_OUTPUT_MODE_MESSAGING}, + taskID{taskID}, + m_jobIndex{jobIndex}, + bNewWorkerEvent{false} { - if (m_inst == 0){ - m_inst = new SimulationMessaging(); - } - return(m_inst); + this->curlHandler = new CurlProxy(simKey, taskID, jobIndex, vcusername, broker, ttl_low, ttl_high); + time(&this->lastSentEventTime); } - -void SimulationMessaging::sendStatus() { - WorkerEvent * workerEvent = 0; //set to null pointer - for (;;) { - { //scope for locking mutex - WorkerEventLocker locker(*this); - if (events.size( ) > 0 ) { - workerEvent = events.front( ); - if (!events.empty()) - { - events.erase(events.begin()); - } - } - else { - return; - } - } //unlocks mutex - - if (workerEventOutputMode == WORKEREVENT_OUTPUT_MODE_STDOUT) { - switch (workerEvent->status) { - case JOB_DATA: - printf("[[[data:%lg]]]", workerEvent->timepoint); - fflush(stdout); - break; - case JOB_PROGRESS: - printf("[[[progress:%lg%%]]]", workerEvent->progress * 100.0); - fflush(stdout); - break; - case JOB_STARTING: - std::cout<< workerEvent->eventMessage << std::endl; - break; - case JOB_COMPLETED: - std::cerr << "Simulation Complete in Main() ... " << std::endl; - break; - case JOB_FAILURE: - std::cerr << workerEvent->eventMessage << std::endl; - break; - } - return; - - } -#ifdef USE_MESSAGING - /* get a curl handle */ - CURL* curl = curl_easy_init(); - if(curl) { - // Documentation for the ActiveMQ restful API is missing, must see source code - // - // https://github.com/apache/activemq/blob/master/activemq-web/src/main/java/org/apache/activemq/web/MessageServlet.java - // https://github.com/apache/activemq/blob/master/activemq-web/src/main/java/org/apache/activemq/web/MessageServletSupport.java - // - // currently, the "web" api seems to use the same credentials as the "web console" ... defaults to admin:admin. - // TODO: pass in credentials, and protect them better (consider HTTPS). - // - /* - PROPERTIES="JMSDeliveryMode=persistent&JMSTimeToLive=3000" - PROPERTIES="${PROPERTIES}&SimKey=12446271133&JobIndex=0&TaskID=0&UserName=schaff" - PROPERTIES="${PROPERTIES}&MessageType=WorkerEvent&WorkerEvent_Status=1001&WorkerEvent_StatusMsg=Running" - PROPERTIES="${PROPERTIES}&WorkerEvent_TimePoint=2.0&WorkerEvent_Progress=0.4&HostName=localhost" - curl -XPOST "http://admin:admin@`hostname`:8165/api/message/workerEvent?type=queue&${PROPERTIES}" - */ - std::stringstream ss_url; - - // ss_url << "http://" << m_smqusername << ":" << m_password << "@" << m_broker << "/api/message/workerEvent?type=queue&"; - ss_url << "http://" << "admin" << ":" << "admin" << "@" << m_broker << "/api/message/workerEvent?type=queue&"; - - switch (workerEvent->status) { - case JOB_DATA: - ss_url << PRIORITY_PROPERTY << "=" << PRIORITY_DEFAULT_VALUE << "&"; - ss_url << TIMETOLIVE_PROPERTY << "=" << m_ttl_lowPriority << "&"; - ss_url << DELIVERYMODE_PROPERTY << "=" << DELIVERYMODE_NONPERSISTENT_VALUE << "&"; - break; - case JOB_PROGRESS: - ss_url << PRIORITY_PROPERTY << "=" << PRIORITY_DEFAULT_VALUE << "&"; - ss_url << TIMETOLIVE_PROPERTY << "=" << m_ttl_lowPriority << "&"; - ss_url << DELIVERYMODE_PROPERTY << "=" << DELIVERYMODE_NONPERSISTENT_VALUE << "&"; - break; - case JOB_STARTING: - ss_url << PRIORITY_PROPERTY << "=" << PRIORITY_DEFAULT_VALUE << "&"; - ss_url << TIMETOLIVE_PROPERTY << "=" << m_ttl_highPriority << "&"; - ss_url << DELIVERYMODE_PROPERTY << "=" << DELIVERYMODE_PERSISTENT_VALUE << "&"; - break; - case JOB_COMPLETED: - ss_url << PRIORITY_PROPERTY << "=" << PRIORITY_DEFAULT_VALUE << "&"; - ss_url << TIMETOLIVE_PROPERTY << "=" << m_ttl_highPriority << "&"; - ss_url << DELIVERYMODE_PROPERTY << "=" << DELIVERYMODE_PERSISTENT_VALUE << "&"; - break; - case JOB_FAILURE: - ss_url << PRIORITY_PROPERTY << "=" << PRIORITY_DEFAULT_VALUE << "&"; - ss_url << TIMETOLIVE_PROPERTY << "=" << m_ttl_highPriority << "&"; - ss_url << DELIVERYMODE_PROPERTY << "=" << DELIVERYMODE_PERSISTENT_VALUE << "&"; - break; - } - - ss_url << MESSAGE_TYPE_PROPERTY << "=" << MESSAGE_TYPE_WORKEREVENT_VALUE << "&"; - ss_url << USERNAME_PROPERTY << "=" << m_vcusername << "&"; - ss_url << HOSTNAME_PROPERTY << "=" << m_hostname << "&"; - ss_url << SIMKEY_PROPERTY << "=" << m_simKey << "&"; - ss_url << TASKID_PROPERTY << "=" << m_taskID << "&"; - ss_url << JOBINDEX_PROPERTY << "=" << m_jobIndex << "&"; - - ss_url << WORKEREVENT_STATUS << "=" << workerEvent->status << "&"; - - char* revisedMsg = workerEvent->eventMessage; - if (revisedMsg != NULL) { - revisedMsg = trim(revisedMsg); - if (strlen(revisedMsg) > 2048) { - revisedMsg[2047] = 0; //status message is only 2048 chars long in database - } - - for (int i = 0; i < (int) strlen(revisedMsg); i++) { - switch (revisedMsg[i]) { - case '\r': - case '\n': - case '\'': - case '\"': - revisedMsg[i] = ' '; - break; - // these characters are not valid both in database and in messages as a property - } - } - } - if (revisedMsg != NULL) { - char *output = curl_easy_escape(curl, revisedMsg, strlen(revisedMsg)); - if(output) { - ss_url << WORKEREVENT_STATUSMSG << "=" << output << "&"; - curl_free(output); - } - } - - ss_url << WORKEREVENT_PROGRESS << "=" << workerEvent->progress << "&"; - ss_url << WORKEREVENT_TIMEPOINT << "=" << workerEvent->timepoint; - - std::string s_url = ss_url.str(); - const char* messaging_http_url = s_url.c_str(); - curl_easy_setopt(curl, CURLOPT_URL, messaging_http_url); - - std::cout << "curl -XPOST " << messaging_http_url << std::endl; - - - // - // print message to stdout - // - std::cout << "!!!SimulationMessaging::sendStatus [" << (long)m_simKey << ":" << getStatusString(workerEvent->status); - if (revisedMsg != NULL) { - std::cout << ":" << revisedMsg; - } else { - std::cout << ":" << workerEvent->progress << ":" << workerEvent->timepoint; - } - std::cout << "]" << std::endl; - - - - // std::stringstream ss_body; - // ss_body << "empty message body"; // one way to force a POST verb with libcurl, probably better ways. - // std::string s_body = ss_body.str(); - // const char* postfields = s_body.c_str(); - curl_easy_setopt(curl, CURLOPT_POSTFIELDS, ""); - - - // Perform the request, res will get the return code - CURLcode res = curl_easy_perform(curl); - // Check for errors - if(res != CURLE_OK) - fprintf(stderr, "curl_easy_perform() failed: %s\n", - curl_easy_strerror(res)); - - // always cleanup - curl_easy_cleanup(curl); - } - - - - time(&lastSentEventTime); - if (workerEvent->status == JOB_COMPLETED || workerEvent->status == JOB_FAILURE) { -#if ( defined(_WIN32) || defined(_WIN64) ) - SetEvent(hMessagingThreadEndEvent); -#else // UNIX - std::cout << "!!!thread exiting" << std::endl; - pthread_exit(NULL); -#endif - } -#else - throw "OUPUT_MODE_STANDOUT must be using if not using messaging!"; #endif - } - delete workerEvent; -} - -void SimulationMessaging::setWorkerEvent(WorkerEvent* arg_workerEvent) { - if (m_inst == 0) { - throw "SimulationMessaging is not initialized"; - } - if (workerEventOutputMode == WORKEREVENT_OUTPUT_MODE_STDOUT) { - events.push_back(arg_workerEvent); - sendStatus(); - return; - } -#ifdef USE_MESSAGING - else { - bool ifset = true; - bool iftry = false; - bool critical = criticalDelivery(*arg_workerEvent); - if (!critical) { - iftry = true; - // - // for portability, if not POSIX, time_t not guaranteed to be in seconds - // add WORKEREVENT_MESSAGE_MIN_TIME_SECONDS to last event time - // - struct tm nextMessageTime_tm = *localtime( &lastSentEventTime); - nextMessageTime_tm.tm_sec += WORKEREVENT_MESSAGE_MIN_TIME_SECONDS; // add MIN_TIME seconds to the time - time_t nextMessageTime = mktime( &nextMessageTime_tm); // normalize it - time_t currTime; - time(&currTime); - if (currTime <= nextMessageTime){ - ifset = false; - } - } - - if (ifset) { - { //scope for lock - WorkerEventLocker locker(*this,iftry); - if (locker.locked) { - events.push_back(arg_workerEvent); - } - } //unlock worker event -#if ( defined(_WIN32) || defined(_WIN64) ) - SetEvent(hNewWorkerEvent); -#else //UNIX - pthread_mutex_lock(&mutex_cond_workerEvent); - bNewWorkerEvent = true; - pthread_cond_signal(&cond_workerEvent); - pthread_mutex_unlock(&mutex_cond_workerEvent); -#endif - } - } -#else - throw "OUPUT_MODE_STANDOUT must be using if not using messaging!"; -#endif -} -#ifdef USE_MESSAGING -/** - * Keep trying to start the connection. Once it is established, - * setup the message handlers and publishers. (This is synchronized - * because it can be called from the main thread, and from the - * onException handler.) - */ -void SimulationMessaging::setupConnection () { //synchronized - // lockMessaging(); - // unlockMessaging(); +SimulationMessaging::~SimulationMessaging() noexcept{ + delete this->curlHandler; } -void SimulationMessaging::cleanup(){ +SimulationMessaging* SimulationMessaging::getInstVar() { + return SimulationMessaging::m_inst; } +SimulationMessaging* SimulationMessaging::create(){ + if (SimulationMessaging::m_inst == NULL) SimulationMessaging::m_inst = new SimulationMessaging(); -/** - * Suspend the current thread for "duration" milliseconds. - */ -void SimulationMessaging::delay(int duration) -{ -#if ( defined(_WIN32) || defined(_WIN64) ) - Sleep(duration); -#else // UNIX - sleep(duration/1000); -#endif + return SimulationMessaging::m_inst; } -bool SimulationMessaging::lockMessaging() -{ -#if ( defined(_WIN32) || defined(_WIN64) ) - EnterCriticalSection(&lockForMessaging); - return true; -#else // UNIX - if (pthread_mutex_lock(&mutex_messaging)) { - std::cout << "Cannot acquire mutex, fatal error." << std::endl; - exit(1); - } -#endif -} +void SimulationMessaging::sendStatus(WorkerEvent* event) { + if (WORKEREVENT_OUTPUT_MODE_STDOUT & workerEventOutputMode) SimulationMessaging::sendStdOutStatus(event); + if (WORKEREVENT_OUTPUT_MODE_MESSAGING & workerEventOutputMode) this->curlHandler->sendStatus(event); -void SimulationMessaging::unlockMessaging() -{ -#if ( defined(_WIN32) || defined(_WIN64) ) - LeaveCriticalSection(&lockForMessaging); -#else // UNIX - pthread_mutex_unlock(&mutex_workerEvent); -#endif + time(&lastSentEventTime); // Update the time var } +void SimulationMessaging::keepAlive() { + if (workerEventOutputMode & WORKEREVENT_OUTPUT_MODE_MESSAGING) this->curlHandler->keepAlive(); -bool SimulationMessaging::lockWorkerEvent(bool bTry) -{ -#if ( defined(_WIN32) || defined(_WIN64) ) - if (bTry) { - return TryEnterCriticalSection(&lockForWorkerEvent)!=0; - } - try { - EnterCriticalSection(&lockForWorkerEvent); - } catch (...) { - return false; - } - return true; -#else // UNIX - if (bTry) { - return (pthread_mutex_trylock(&mutex_workerEvent) == 0); - } - if (pthread_mutex_lock(&mutex_workerEvent) != 0) { - return false; - } - return true; -#endif -} - -void SimulationMessaging::unlockWorkerEvent() -{ -#if ( defined(_WIN32) || defined(_WIN64) ) - LeaveCriticalSection(&lockForWorkerEvent); -#else // UNIX - pthread_mutex_unlock(&mutex_workerEvent); -#endif + time(&lastSentEventTime); // Update the time var } -char* SimulationMessaging::trim(char* str) { - int leftIndex, rightIndex; - int len = (int)strlen(str); - for (leftIndex = 0; leftIndex < len; leftIndex ++) { // remove leading spaces - char c = str[leftIndex]; - if (c != ' ' && c != '\n' && c != '\r') { +void SimulationMessaging::sendStdOutStatus(WorkerEvent* workerEvent) { + switch (workerEvent->status) { + case JobEvent::JOB_DATA: + std::cout << std::format("[[[data:{}]]])", workerEvent->timepoint) << std::endl; + // printf("[[[data:%lg]]]", workerEvent->timepoint); + // fflush(stdout); break; - } - } - for (rightIndex = len - 1; rightIndex >= 0; rightIndex --) { // remove trailing spaces and new line and carriage return - char c = str[rightIndex]; - if (c != ' ' && c != '\n' && c != '\r') { + case JobEvent::JOB_PROGRESS: + std::cout << std::format("[[[progress:{}%]]]", workerEvent->progress * 100.0) << std::endl; + // printf("[[[progress:%lg%%]]]", workerEvent->progress * 100.0); + // fflush(stdout); break; - } - } - - len = rightIndex - leftIndex + 2; - if (len <= 0) { - return 0; - } - - char* newstr = new char[len]; - memset(newstr, 0, len * sizeof(char)); - strncpy(newstr, str + leftIndex, len - 1); - - return newstr; + case JobEvent::JOB_STARTING: + std::cout<< workerEvent->eventMessage << std::endl; + break; + case JobEvent::JOB_COMPLETED: + std::cerr << "Simulation Complete in Main() ... " << std::endl; + break; + case JobEvent::JOB_FAILURE: + std::cerr << workerEvent->eventMessage << std::endl; + break; + default: + throw std::runtime_error(std::format("SimulationMessaging::sendStatus: `{}` is not recognized",JobEvent::toString(workerEvent->status))); + } +} + +void SimulationMessaging::setWorkerEvent(JobEvent::Status status, const char *eventMessage) { + this->eventHandler.enqueue(status, eventMessage); +} + +void SimulationMessaging::setWorkerEvent(const JobEvent::Status status, const double progress, const double timepoint) { + this->eventHandler.enqueue(status, progress, timepoint); + // bool critical = criticalDelivery(*arg_workerEvent); + // if (!critical) { + // iftry = true; + // // + // // for portability, if not POSIX, time_t not guaranteed to be in seconds + // // add WORKEREVENT_MESSAGE_MIN_TIME_SECONDS to last event time + // // + // tm nextMessageTime_tm = *localtime( &lastSentEventTime); + // nextMessageTime_tm.tm_sec += WORKEREVENT_MESSAGE_MIN_TIME_SECONDS; // add MIN_TIME seconds to the time + // time_t nextMessageTime = mktime( &nextMessageTime_tm); // normalize it + // time_t currTime; + // time(&currTime); + // if (currTime <= nextMessageTime){ + // isTimeForNextMessage = false; + // } + // } + // + // if (isTimeForNextMessage) { + // { //scope for lock + // WorkerEventLocker locker(*this,iftry); + // if (locker.locked) { + // events.push_back(arg_workerEvent); + // } + // } //unlock worker event + // pthread_mutex_lock(&mutex_cond_workerEvent); + // bNewWorkerEvent = true; + // pthread_cond_signal(&cond_workerEvent); + // pthread_mutex_unlock(&mutex_cond_workerEvent); + // } +} + +void SimulationMessaging::setWorkerEvent(JobEvent::Status status, const double progress, const double timepoint, const char *eventMessage) { + this->eventHandler.enqueue(status, progress, timepoint, eventMessage); } -void SimulationMessaging::keepAlive() { - if (workerEventOutputMode == WORKEREVENT_OUTPUT_MODE_MESSAGING) { - - - CURL* curl = curl_easy_init(); - if(curl) { - // First set the URL that is about to receive our POST. This URL can - // just as well be a https:// URL if that is what should receive the - // data. - curl_easy_setopt(curl, CURLOPT_URL, "http://localhost:6161/message/workerevent?readTimeout=20000&type=queue"); - - - // Now specify the POST data - std::stringstream ss; - - //status - // msg->setIntProperty(WORKEREVENT_STATUS, JOB_WORKER_ALIVE); - ss << WORKEREVENT_STATUS << "=" << JOB_WORKER_ALIVE; - - std::string x = ss.str(); - const char* postfields = x.c_str(); - - curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postfields); // e.g. postfields="name=daniel&project=curl" - - // Perform the request, res will get the return code - CURLcode res = curl_easy_perform(curl); - // Check for errors - if(res != CURLE_OK) - fprintf(stderr, "curl_easy_perform() failed: %s\n", - curl_easy_strerror(res)); - - // always cleanup - curl_easy_cleanup(curl); - } - - time(&lastSentEventTime); - } +void SimulationMessaging::waitUntilFinished() { + this->eventHandler.requestStopAndWaitForIt(); + // if (workerEventOutputMode == WORKEREVENT_OUTPUT_MODE_STDOUT) return; + // #ifdef USE_MESSAGING + // std::cout << "!!!waiting for thread to exit" << std::endl; + // pthread_join(newWorkerEventThread, NULL); + // std::cout << "!!Threads joined successfully" << std::endl; + // #endif } -const char* SimulationMessaging::getStatusString(int status) { - switch (status) { - case JOB_STARTING: - return "JOB_STARTING"; - - case JOB_PROGRESS: - return "JOB_PROGRESS"; - - case JOB_DATA: - return "JOB_DATA"; - - case JOB_COMPLETED: - return "JOB_COMPLETED"; - - case JOB_WORKER_ALIVE: - return "JOB_WORKER_ALIVE"; - - case JOB_FAILURE: - return "JOB_FAILURE"; - - default: - return "Unknown status"; - } -} +#ifdef USE_MESSAGING -SimulationMessaging* SimulationMessaging::create(const char* broker, const char* smqusername, const char* passwd, const char* qname, const char* tname, const char* vcusername, int simKey, int jobIndex, int taskID, int ttl_low, int ttl_high) -{ +SimulationMessaging* SimulationMessaging::create(const char* broker, const char* smqusername, const char* passwd, const char* qname, const char* tname, const char* vcusername, int simKey, int jobIndex, int taskID, int ttl_low, int ttl_high){ if (m_inst != NULL && m_inst->workerEventOutputMode == WORKEREVENT_OUTPUT_MODE_STDOUT) { delete m_inst; m_inst = NULL; } if (m_inst == NULL){ - m_inst = new SimulationMessaging(broker, smqusername, passwd, qname, tname, vcusername, simKey, jobIndex, taskID, ttl_low, ttl_high); + m_inst = new SimulationMessaging(broker, vcusername, simKey, jobIndex, taskID, ttl_low, ttl_high); } return(m_inst); } -void SimulationMessaging::waitUntilFinished() { - if (workerEventOutputMode == WORKEREVENT_OUTPUT_MODE_STDOUT) { - return; - } -#if ( defined(_WIN32) || defined(_WIN64) ) - DWORD dwWaitResult = WaitForSingleObject(hMessagingThreadEndEvent, INFINITE); - switch (dwWaitResult) { - case WAIT_OBJECT_0: - case WAIT_TIMEOUT: - break; - - default: - cout << "Wait error: " << GetLastError() << endl; - break; - } -#else - std::cout << "!!!waiting for thread to exit" << std::endl; - pthread_join(newWorkerEventThread, NULL); -#endif -} - -void* startMessagingThread(void* param); - -void SimulationMessaging::start() { - if (workerEventOutputMode == WORKEREVENT_OUTPUT_MODE_STDOUT) { - return; - } - if (bStarted) { - return; - } - bStarted = true; - -#if ( defined(_WIN32) || defined(_WIN64) ) - HANDLE hThread; - DWORD IDThread; - - char messagingThreadEndEventName[256]; - char newWorkerEventName[256]; - sprintf(messagingThreadEndEventName, "MessagingThreadEndEvent_%ld_%d_%d", m_simKey, m_jobIndex, m_taskID); - sprintf(newWorkerEventName, "NewWorkerEvent_%ld_%d_%d", m_simKey, m_jobIndex, m_taskID); - - // Create a manual-reset event object. The master thread sets - // this to nonsignaled when it writes to the shared buffer. - hMessagingThreadEndEvent = CreateEvent( - NULL, // no security attributes - true, // manual-reset event - false, // initial state is nonsignaled - messagingThreadEndEventName // object name - ); - - if (hMessagingThreadEndEvent == NULL) { - throw "CreateEvent failed for new worker event"; - } - - if (GetLastError() == ERROR_ALREADY_EXISTS) { - throw "Event for messagingThreadEndEvent already exists"; - } - - hNewWorkerEvent = CreateEvent( - NULL, // no security attributes - true, // manual-reset event - false, // initial state is nonsignaled - newWorkerEventName // object name - ); - - if (hNewWorkerEvent == NULL) { - throw "CreateEvent failed for new worker event"; - } - - if (GetLastError() == ERROR_ALREADY_EXISTS) { - throw "Event for newWorkerEvent already exists"; - } - - // Create multiple threads and an auto-reset event object - // for each thread. Each thread sets its event object to - // signaled when it is not reading from the shared buffer. - - hThread = CreateThread(NULL, 0, - (LPTHREAD_START_ROUTINE) startMessagingThread, - &hNewWorkerEvent, // pass event handle - 0, &IDThread); - if (hThread == NULL) - { - throw "CreateThread failed for messaging thread"; - } -#else // UNIX - int retv = pthread_create(&newWorkerEventThread, NULL, startMessagingThread, this); -#endif -} - -void* startMessagingThread(void* lpParam){ - SimulationMessaging::getInstVar()->setupConnection(); - -#if ( defined(_WIN32) || defined(_WIN64) ) - DWORD dwWaitResult; - HANDLE hEvent; - - hEvent = *(HANDLE*)lpParam; // thread's read event - - while (true) { - dwWaitResult = WaitForSingleObject(hEvent, 5*60*1000); // 5 min wait - - switch (dwWaitResult) { - case WAIT_OBJECT_0: - { - WorkerEvent* sentWorkerEvent = SimulationMessaging::getInstVar()->sendStatus(); - SimulationMessaging::getInstVar()->lockWorkerEvent(); - if (sentWorkerEvent != NULL - && SimulationMessaging::getInstVar()->getWorkerEvent() != NULL - && sentWorkerEvent->equals(SimulationMessaging::getInstVar()->getWorkerEvent())) { - ResetEvent(hEvent); - } - SimulationMessaging::getInstVar()->unlockWorkerEvent(); - delete sentWorkerEvent; - } - break; - - // An error occurred. - case WAIT_TIMEOUT: - SimulationMessaging::getInstVar()->keepAlive(); - break; - - default: - std::cout<< "Wait error: " << GetLastError() << endl; - break; - } - } -#else // UNIX - int waitReturn = 0; - struct timespec timeout; - struct timeval now; - struct timeval start; - SimulationMessaging* simMessaging = (SimulationMessaging*)lpParam; - - while (true) { - waitReturn = 1; - gettimeofday(&start, NULL); - timeout.tv_sec = start.tv_sec; - // condition might be signalled before this thread gets blocked - // so this thread might miss signal and doesn't get wakened - // if this happens don't want to wait too long to check if there - // is new worker event. This is the reason for the loop and 2 sec timeout - pthread_mutex_lock(&simMessaging->mutex_cond_workerEvent); - while (timeout.tv_sec - start.tv_sec < 5 * 60) { - // at this point, there are two possibilities - // 1. this thread has been awakened, waited < 2 secs, waitReturn is 0 - // 2. this thread has waited full 2 sec timout, waitReturn is non zero - // in either case, check if there is new worker event. - if (simMessaging->bNewWorkerEvent) { - simMessaging->bNewWorkerEvent = false; - waitReturn = 0; - break; - } - gettimeofday(&now, NULL); - timeout.tv_sec = now.tv_sec + 2; - timeout.tv_nsec = now.tv_usec * 1000; - waitReturn = pthread_cond_timedwait(&simMessaging->cond_workerEvent, &simMessaging->mutex_cond_workerEvent, &timeout); - } - pthread_mutex_unlock(&simMessaging->mutex_cond_workerEvent); - - switch (waitReturn) { - case 0: { // new event - simMessaging->sendStatus(); - break; - } - - case ETIMEDOUT: // time out - simMessaging->keepAlive(); - break; - - default: - std::cout<< "Wait error: " << waitReturn << std::endl; - break; - } - } -#endif -} +// void SimulationMessaging::start() { +// if (workerEventOutputMode == WORKEREVENT_OUTPUT_MODE_STDOUT) { +// return; +// } +// if (bStarted) { +// return; +// } +// bStarted = true; +// int retv = pthread_create(&newWorkerEventThread, NULL, startMessagingThread, this); +// } + +// void* startMessagingThread(void* lpParam){ +// int waitReturn = 0; +// struct timespec timeout; +// struct timeval now; +// struct timeval start; +// SimulationMessaging* simMessaging = (SimulationMessaging*)lpParam; +// +// while (true) { +// waitReturn = 1; +// gettimeofday(&start, NULL); +// timeout.tv_sec = start.tv_sec; +// // condition might be signalled before this thread gets blocked +// // so this thread might miss signal and doesn't get wakened +// // if this happens don't want to wait too long to check if there +// // is new worker event. This is the reason for the loop and 2 sec timeout +// pthread_mutex_lock(&simMessaging->mutex_cond_workerEvent); +// while (timeout.tv_sec - start.tv_sec < 5 * 60) { +// // at this point, there are two possibilities +// // 1. this thread has been awakened, waited < 2 secs, waitReturn is 0 +// // 2. this thread has waited full 2 sec timout, waitReturn is non zero +// // in either case, check if there is new worker event. +// if (simMessaging->bNewWorkerEvent) { +// simMessaging->bNewWorkerEvent = false; +// waitReturn = 0; +// break; +// } +// gettimeofday(&now, NULL); +// timeout.tv_sec = now.tv_sec + 2; +// timeout.tv_nsec = now.tv_usec * 1000; +// waitReturn = pthread_cond_timedwait(&simMessaging->cond_workerEvent, &simMessaging->mutex_cond_workerEvent, &timeout); +// } +// pthread_mutex_unlock(&simMessaging->mutex_cond_workerEvent); +// +// switch (waitReturn) { +// case 0: { // new event +// simMessaging->sendStatus(); +// break; +// } +// +// case ETIMEDOUT: // time out +// simMessaging->keepAlive(); +// break; +// +// default: +// std::cout<< "Wait error: " << waitReturn << std::endl; +// break; +// } +// } +// } #endif diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 686c973b3..9ee8867de 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -7,7 +7,7 @@ FetchContent_Declare( GIT_REPOSITORY https://github.com/google/googletest.git GIT_TAG v1.17.0 ) -if (WINDOWS) +if (WIN32) # For Windows: Prevent overriding the parent project's compiler/linker settings set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) endif() @@ -19,6 +19,7 @@ include(GoogleTest) add_executable(unit_tests unit/smoke_test.cpp unit/hello_test.cpp + unit/message_processing_test.cpp ) target_link_libraries(unit_tests PRIVATE IDAWin diff --git a/tests/unit/message_processing_test.cpp b/tests/unit/message_processing_test.cpp new file mode 100644 index 000000000..088c06189 --- /dev/null +++ b/tests/unit/message_processing_test.cpp @@ -0,0 +1,66 @@ +// +// Created by Logan Drescher on 12/1/25. +// +#include +#include +#include +#include + +#include "VCellSundialsSolver.h" + +static std::vector eventTracker; + +void appendNextIndex(const WorkerEvent* event); +std::vector generateFibonacci(int length); +long sequenceComparison(const std::vector &sequence1, const std::vector &sequence2); + +TEST(MessageProcessingTest, MessagesAreProcessed) { + + MessageEventManager eventManager{[](WorkerEvent* event)->void{appendNextIndex(event);}}; + const int NUM_ITERATIONS = 100; + for (int i = 0; i < NUM_ITERATIONS; ++i) { + JobEvent::Status status; + switch (i + 1) { + case 1: status = JobEvent::JOB_STARTING; break; + case NUM_ITERATIONS: status = JobEvent::JOB_COMPLETED; break; + default: status = JobEvent::JOB_PROGRESS; break; + } + eventManager.enqueue(status, (i + 1) / 100.0, i, std::to_string(i).c_str()); + } + eventManager.requestStopAndWaitForIt(); + std::vector expectedResults = generateFibonacci(NUM_ITERATIONS); + ASSERT_TRUE(!sequenceComparison(eventTracker, expectedResults)); +} + +void appendNextIndex(const WorkerEvent* event) { + if (eventTracker.empty()) { eventTracker.emplace_back("0"); return; } + if (1 == eventTracker.size()) { eventTracker.emplace_back("1"); return; } + + const long firstValue = std::stol(eventTracker[eventTracker.size() - 2]); + const long secondValue = std::stol(eventTracker[eventTracker.size() - 1]); + const std::string nextValue{std::to_string(firstValue + secondValue)}; + eventTracker.push_back(nextValue); +} + +std::vector generateFibonacci(const int length) { + std::vector sequence; + for (int i = 0; i < length; ++i) { + if (i == 0) { sequence.emplace_back("0"); continue; } + if (i == 1) { sequence.emplace_back("1"); continue; } + const long firstValue = std::stol(sequence[sequence.size() - 2]); + const long secondValue = std::stol(sequence[sequence.size() - 1]); + const std::string nextValue{std::to_string(firstValue + secondValue)}; + sequence.push_back(nextValue); + } + return sequence; +} + +long sequenceComparison(const std::vector &sequence1, const std::vector &sequence2) { + if (const size_t comp = sequence2.size() - sequence1.size(); comp) return static_cast(comp); + for (size_t i = 0; i < sequence1.size(); ++i) { + const long firstValue = std::stol(sequence1[i]); + const long secondValue = std::stol(sequence2[i]); + if (const long comp = secondValue - firstValue; comp) return comp; + } + return 0; +} \ No newline at end of file From a8cc4ed724a69a0510bf493461ae35df4090192e Mon Sep 17 00:00:00 2001 From: Logan Drescher Date: Tue, 9 Dec 2025 13:37:44 -0500 Subject: [PATCH 10/34] downgraded j-thread for better compatability --- Dockerfile | 29 ++++++++++--------- .../include/VCELL/MessageEventManager.h | 2 +- .../include/VCELL/SimulationMessaging.h | 10 +------ VCellMessaging/src/JobEventStatus.cpp | 1 + VCellMessaging/src/MessageEventManager.cpp | 1 + VCellMessaging/src/SimulationMessaging.cpp | 4 +-- 6 files changed, 21 insertions(+), 26 deletions(-) diff --git a/Dockerfile b/Dockerfile index f6476148b..5565be3ba 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,29 +2,32 @@ FROM debian:trixie-slim AS build SHELL ["/bin/bash", "-c"] RUN apt-get -y update && apt-get install -y apt-utils && apt-get remove --purge gcc -<<<<<<< HEAD -RUN apt-get install -y -qq -o=Dpkg::Use-Pty=0 clang mold ninja-build cmake libc++-dev libc++abi-dev libcurl4-openssl-dev \ - git curl wget ca-certificates python3 python3-pip -======= -RUN apt-get install -y -qq -o=Dpkg::Use-Pty=0 mold ninja-build cmake clang-18 clang++-18 clang-tools-18 libc++-18-dev libc++abi-18-dev \ - libcurl4-openssl-dev git curl wget ca-certificates python3 python3-pip ->>>>>>> 88cc3915 (Fixed bad parsing when looking for messaging params) +# line 1 => build dependencies +# line 2 => build tools +# line 3 => dependencies +RUN apt-get install -y -qq -o=Dpkg::Use-Pty=0 \ + git curl wget ca-certificates python3 python3-pip \ + mold ninja-build cmake clang-19 clang++-19 clang-tools-19 libc++-19-dev libc++abi-19-dev \ + libspdlog-dev libcurl4-openssl-dev + RUN rm $(which gcc) || true RUN rm $(which g++) || true RUN rm -rf /var/lib/apt/lists/* && rm /usr/bin/ld -#RUN ln -s $(which clang-18) /usr/bin/clang -#RUN ln -s $(which clang++-18) /usr/bin/clang++ RUN ln -s $(which mold) /usr/bin/ld - +RUN clang-19 --version +RUN ln -s $(which clang-19) /usr/bin/clang +RUN clang++-19 --version +RUN ln -s $(which clang++-19) /usr/bin/clang++ COPY . /vcellroot RUN mkdir -p /vcellroot/build/bin WORKDIR /vcellroot/build -RUN clang++ --version -RUN /usr/bin/cmake .. -G Ninja -DOPTION_TARGET_MESSAGING=ON -DOPTION_TARGET_DOCS=OFF +RUN /usr/bin/cmake .. -G Ninja -DOPTION_TARGET_MESSAGING=ON -DOPTION_TARGET_DOCS=OFF -DOPTION_TEST_WITH_LOCALHOST=ON +#RUN /usr/bin/cmake .. -G Ninja -DOPTION_TARGET_MESSAGING=ON -DOPTION_TARGET_DOCS=OFF -DCMAKE_CXX_FLAGS="-fexperimental-library" # `experimental-library` needed for new j-thread feature! RUN ninja --verbose -RUN ctest -VV +RUN ctest -VV --output-on-failure +RUN rm /vcellroot/build/bin/unit_tests WORKDIR /vcellroot/build/bin ENV PATH="/vcellroot/build/bin:${PATH}" \ No newline at end of file diff --git a/VCellMessaging/include/VCELL/MessageEventManager.h b/VCellMessaging/include/VCELL/MessageEventManager.h index d9d425ea9..b00470f15 100644 --- a/VCellMessaging/include/VCELL/MessageEventManager.h +++ b/VCellMessaging/include/VCELL/MessageEventManager.h @@ -54,7 +54,7 @@ class MessageEventManager { std::queue eventQueue; std::mutex queuetex; - std::jthread eventQueueProcessingWorkerThread; + std::thread eventQueueProcessingWorkerThread; //TODO: make a `std::jthread` once compilers catch up with standard std::condition_variable needMessageProcessingForeman; std::function sendUpdateFunction; }; diff --git a/VCellMessaging/include/VCELL/SimulationMessaging.h b/VCellMessaging/include/VCELL/SimulationMessaging.h index d12be5dfa..92a672e04 100644 --- a/VCellMessaging/include/VCELL/SimulationMessaging.h +++ b/VCellMessaging/include/VCELL/SimulationMessaging.h @@ -1,10 +1,8 @@ #ifndef _SIMULATIONMESSAGING_H_ #define _SIMULATIONMESSAGING_H_ -#include #include -#include -#include + #include "CurlProxyClasses.h" #include "MessageEventManager.h" @@ -94,12 +92,6 @@ class SimulationMessaging { int taskID; int m_jobIndex; time_t lastSentEventTime; - - pthread_t newWorkerEventThread; - pthread_mutex_t mutex_messaging; - pthread_mutex_t mutex_workerEvent; - pthread_mutex_t mutex_cond_workerEvent; - pthread_cond_t cond_workerEvent; bool bNewWorkerEvent; }; diff --git a/VCellMessaging/src/JobEventStatus.cpp b/VCellMessaging/src/JobEventStatus.cpp index 22281f510..bff233567 100644 --- a/VCellMessaging/src/JobEventStatus.cpp +++ b/VCellMessaging/src/JobEventStatus.cpp @@ -2,6 +2,7 @@ // Created by Logan Drescher on 11/25/25. // #include +#include std::string JobEvent::toString(const Status status) { switch (status) { diff --git a/VCellMessaging/src/MessageEventManager.cpp b/VCellMessaging/src/MessageEventManager.cpp index 4bc775328..fae2ab342 100644 --- a/VCellMessaging/src/MessageEventManager.cpp +++ b/VCellMessaging/src/MessageEventManager.cpp @@ -15,6 +15,7 @@ MessageEventManager::MessageEventManager(std::function sendU MessageEventManager::~MessageEventManager() { if (!this->stopRequested) this->requestStopAndWaitForIt(); + this->eventQueueProcessingWorkerThread.join(); } void MessageEventManager::enqueue(const JobEvent::Status status, const double progress, const double timepoint, const char *eventMessage) { diff --git a/VCellMessaging/src/SimulationMessaging.cpp b/VCellMessaging/src/SimulationMessaging.cpp index 47d5c729e..5dea90ff4 100644 --- a/VCellMessaging/src/SimulationMessaging.cpp +++ b/VCellMessaging/src/SimulationMessaging.cpp @@ -1,9 +1,7 @@ #include #include -#include #include -#include -#include +#include #include From c1c0294b5b2e1fc7d8ad714753095a3ba9fd42e2 Mon Sep 17 00:00:00 2001 From: Logan Drescher Date: Wed, 10 Dec 2025 13:32:49 -0500 Subject: [PATCH 11/34] Fixed remaining memory leaks! Now leak free! --- IDAWin/SundialsSolverInterface.cpp | 60 ++++++++++++++++++++++++++---- IDAWin/VCellSolverFactory.cpp | 8 +++- 2 files changed, 59 insertions(+), 9 deletions(-) diff --git a/IDAWin/SundialsSolverInterface.cpp b/IDAWin/SundialsSolverInterface.cpp index 7aadee250..09fcebcb8 100644 --- a/IDAWin/SundialsSolverInterface.cpp +++ b/IDAWin/SundialsSolverInterface.cpp @@ -19,21 +19,63 @@ void errExit(int returnCode, const std::string &errorMsg); void activateSolver(std::ifstream& inputFileStream, FILE* outputFile, int taskID) { + int returnCode = 0; + std::string errorMsg; + VCellSolver* targetSolver; + // First try block - create the solver; failure => no need to delete targetSolver! try { - VCellSolver* targetSolver = VCellSolverFactory::produceVCellSolver(inputFileStream, taskID); - targetSolver->solve(nullptr, true, outputFile, VCellSundialsSolver::checkStopRequested); + targetSolver = VCellSolverFactory::produceVCellSolver(inputFileStream, taskID); } catch (const char *ex) { - errExit(-1, ex); + returnCode = -1; + errorMsg = ex; + targetSolver = nullptr; } catch (std::string &ex) { - errExit(-2, ex); + returnCode = -2; + errorMsg = ex; + targetSolver = nullptr; } catch (StoppedByUserException&) { - errExit(0, "Execution Stopped By User"); + returnCode = 0; + errorMsg = "Execution Stopped By User"; + targetSolver = nullptr; } catch (VCell::Exception &ex) { - errExit(-3, ex.getMessage()); + returnCode = -3; + errorMsg = ex.getMessage(); + targetSolver = nullptr; } catch (const std::exception& err) { - errExit(-4, err.what()); + returnCode = -4; + errorMsg = err.what(); + targetSolver = nullptr; } catch (...) { - errExit(-5, "unknown error"); + returnCode = -5; + errorMsg = "Unknown Error Detected"; + targetSolver = nullptr; + } + // second try block - solver is created; must delete it! + if (nullptr != targetSolver) { + try { + targetSolver->solve(nullptr, true, outputFile, VCellSundialsSolver::checkStopRequested); + } catch (const char *ex) { + returnCode = -1; + errorMsg = ex; + // errExit(-1, ex); + } catch (std::string &ex) { + returnCode = -2; + errorMsg = ex; + } catch (StoppedByUserException&) { + returnCode = 0; + errorMsg = "Execution Stopped By User"; + } catch (VCell::Exception &ex) { + returnCode = -3; + errorMsg = ex.getMessage(); + } catch (const std::exception& err) { + returnCode = -4; + errorMsg = err.what(); + } catch (...) { + returnCode = -5; + errorMsg = "Unknown Error Detected"; + } + // !!! DELETE THE SOLVER !!! + delete targetSolver; } // cleanup @@ -41,6 +83,8 @@ void activateSolver(std::ifstream& inputFileStream, FILE* outputFile, int taskID SimulationMessaging::getInstVar()->waitUntilFinished(); delete SimulationMessaging::getInstVar(); } + + if (!errorMsg.empty()) errExit(returnCode, errorMsg); } void errExit(int returnCode, const std::string &errorMsg) { diff --git a/IDAWin/VCellSolverFactory.cpp b/IDAWin/VCellSolverFactory.cpp index 3fe83577b..212508f2e 100644 --- a/IDAWin/VCellSolverFactory.cpp +++ b/IDAWin/VCellSolverFactory.cpp @@ -43,7 +43,13 @@ VCellSolver* VCellSolverFactory::produceVCellSolver(std::ifstream& inputFileStre default: throw new VCell::Exception("Unknown VCellSolver type"); } - desiredSolver->configureFromInput(inputBreakdown); + try { + desiredSolver->configureFromInput(inputBreakdown); + } catch (...) { + delete desiredSolver; + throw; + } + return desiredSolver; } From 180ddd8a1efdf2c28237a4befddcc69e0b3d806a Mon Sep 17 00:00:00 2001 From: Logan Drescher Date: Tue, 16 Dec 2025 11:01:23 -0500 Subject: [PATCH 12/34] Added catch to messaging loop, should give better message and stay alive --- .../include/VCELL/MessageEventManager.h | 1 + VCellMessaging/src/MessageEventManager.cpp | 18 +++++++++++++++++- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/VCellMessaging/include/VCELL/MessageEventManager.h b/VCellMessaging/include/VCELL/MessageEventManager.h index b00470f15..a167e4748 100644 --- a/VCellMessaging/include/VCELL/MessageEventManager.h +++ b/VCellMessaging/include/VCELL/MessageEventManager.h @@ -42,6 +42,7 @@ class MessageEventManager { bool stopWasCalled(); private: + void performQueueProcessing(); void processQueue(); void processEvent(WorkerEvent* event); void enqueue(WorkerEvent*); diff --git a/VCellMessaging/src/MessageEventManager.cpp b/VCellMessaging/src/MessageEventManager.cpp index fae2ab342..20e3024ab 100644 --- a/VCellMessaging/src/MessageEventManager.cpp +++ b/VCellMessaging/src/MessageEventManager.cpp @@ -4,11 +4,14 @@ #include #include #include +#include +#include +#include MessageEventManager::MessageEventManager(std::function sendUpdateFunction): stopRequested{false}, sendUpdateFunction{std::move(sendUpdateFunction)}, - eventQueueProcessingWorkerThread([this]() {this->processQueue();}) // Should auto-start thread + eventQueueProcessingWorkerThread([this]() {this->performQueueProcessing();}) // Should auto-start thread { } @@ -32,6 +35,7 @@ void MessageEventManager::enqueue(const JobEvent::Status status, const char *eve void MessageEventManager::requestStopAndWaitForIt() { std::unique_lock stopRequestedLock{this->stopRequestedMutex}; + if (this->stopRequested) return; this->stopRequested = true; this->needMessageProcessingForeman.notify_all(); this->requestedStopForeman.wait(stopRequestedLock, [this]()->bool{return this->eventQueue.empty();}); @@ -47,6 +51,18 @@ bool MessageEventManager::stopWasCalled() { /// Private Methods /// /////////////////////////////////////////////////// +void MessageEventManager::performQueueProcessing() { + try { + processQueue(); + } catch (std::system_error e) { + std::cerr << std::format("System Error (Code {} - {}): {}", std::to_string(e.code().value()), e.code().category().name(), e.what()) << std::endl; + } catch (std::exception& e) { + std::cerr << e.what() << std::endl; + } catch (...) { + std::cerr << "An unknown exception occurred while processing the event queue!" << std::endl; + } +} + void MessageEventManager::processQueue() { while (true) { WorkerEvent* event; From d2fd72924f475d33a3417c254f7d53ad2ab2691b Mon Sep 17 00:00:00 2001 From: Logan Drescher Date: Tue, 16 Dec 2025 11:25:41 -0500 Subject: [PATCH 13/34] TSAN caught data race; added protective locks --- VCellMessaging/src/MessageEventManager.cpp | 2 ++ tests/unit/message_processing_test.cpp | 24 +++++++++++++++------- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/VCellMessaging/src/MessageEventManager.cpp b/VCellMessaging/src/MessageEventManager.cpp index 20e3024ab..5f9bedef7 100644 --- a/VCellMessaging/src/MessageEventManager.cpp +++ b/VCellMessaging/src/MessageEventManager.cpp @@ -83,8 +83,10 @@ void MessageEventManager::processQueue() { // Wait for the worker to be "prodded" (via `needMessagingForeman.notify_one()`), AND the event queue is not empty // Note that this `wait()` call, by design, unlocks the queue mutex, until it is "prodded". this->needMessageProcessingForeman.wait(shouldBeActiveLock); + std::unique_lock stopRequestedLock{this->stopRequestedMutex}; if (this->eventQueue.empty()) continue; // Probably means we need to check if stop was requested again } + std::unique_lock stopRequestedLock{this->stopRequestedMutex}; event = this->eventQueue.front(); this->eventQueue.pop(); }// END Clock-out Scope // diff --git a/tests/unit/message_processing_test.cpp b/tests/unit/message_processing_test.cpp index 088c06189..548c83738 100644 --- a/tests/unit/message_processing_test.cpp +++ b/tests/unit/message_processing_test.cpp @@ -3,12 +3,15 @@ // #include #include +#include +#include #include #include #include "VCellSundialsSolver.h" static std::vector eventTracker; +static std::mutex eventTrackerMutex; void appendNextIndex(const WorkerEvent* event); std::vector generateFibonacci(int length); @@ -25,7 +28,9 @@ TEST(MessageProcessingTest, MessagesAreProcessed) { case NUM_ITERATIONS: status = JobEvent::JOB_COMPLETED; break; default: status = JobEvent::JOB_PROGRESS; break; } + eventTrackerMutex.lock(); eventManager.enqueue(status, (i + 1) / 100.0, i, std::to_string(i).c_str()); + eventTrackerMutex.unlock(); } eventManager.requestStopAndWaitForIt(); std::vector expectedResults = generateFibonacci(NUM_ITERATIONS); @@ -33,13 +38,18 @@ TEST(MessageProcessingTest, MessagesAreProcessed) { } void appendNextIndex(const WorkerEvent* event) { - if (eventTracker.empty()) { eventTracker.emplace_back("0"); return; } - if (1 == eventTracker.size()) { eventTracker.emplace_back("1"); return; } - - const long firstValue = std::stol(eventTracker[eventTracker.size() - 2]); - const long secondValue = std::stol(eventTracker[eventTracker.size() - 1]); - const std::string nextValue{std::to_string(firstValue + secondValue)}; - eventTracker.push_back(nextValue); + eventTrackerMutex.lock(); + if (eventTracker.empty()) { + eventTracker.emplace_back("0"); + } else if (1 == eventTracker.size()) { + eventTracker.emplace_back("1"); + } else { + const long firstValue = std::stol(eventTracker[eventTracker.size() - 2]); + const long secondValue = std::stol(eventTracker[eventTracker.size() - 1]); + const std::string nextValue{std::to_string(firstValue + secondValue)}; + eventTracker.push_back(nextValue); + } + eventTrackerMutex.unlock(); } std::vector generateFibonacci(const int length) { From 8f69da7c007b7dc96ffd5cfb09d959d88cd030e1 Mon Sep 17 00:00:00 2001 From: Logan Drescher Date: Wed, 17 Dec 2025 13:54:03 -0500 Subject: [PATCH 14/34] simplified event-queue, and fixed mutex-access-before-constructor-finished bug --- .../include/VCELL/MessageEventManager.h | 11 ++-- .../include/VCELL/SimulationMessaging.h | 14 ++--- VCellMessaging/src/MessageEventManager.cpp | 47 ++++++--------- VCellMessaging/src/SimulationMessaging.cpp | 57 ++++++------------- 4 files changed, 45 insertions(+), 84 deletions(-) diff --git a/VCellMessaging/include/VCELL/MessageEventManager.h b/VCellMessaging/include/VCELL/MessageEventManager.h index a167e4748..33bd2381e 100644 --- a/VCellMessaging/include/VCELL/MessageEventManager.h +++ b/VCellMessaging/include/VCELL/MessageEventManager.h @@ -47,17 +47,18 @@ class MessageEventManager { void processEvent(WorkerEvent* event); void enqueue(WorkerEvent*); - std::mutex timeClockMutex; + std::thread eventQueueProcessingWorkerThread; //TODO: make a `std::jthread` once compilers catch up with standard + std::function sendUpdateFunction; + // Critical Resources / Regions + // -> CR #1 - Whether stop has been requested or not bool stopRequested; std::condition_variable requestedStopForeman; std::mutex stopRequestedMutex; + // -> CR #2 - The Event Queue std::queue eventQueue; + std::condition_variable eventQueueForeman; std::mutex queuetex; - - std::thread eventQueueProcessingWorkerThread; //TODO: make a `std::jthread` once compilers catch up with standard - std::condition_variable needMessageProcessingForeman; - std::function sendUpdateFunction; }; #endif //VCELL_ODE_NUMERICS_MESSAGEEVENTQUEUE_H \ No newline at end of file diff --git a/VCellMessaging/include/VCELL/SimulationMessaging.h b/VCellMessaging/include/VCELL/SimulationMessaging.h index 92a672e04..ab6cd04cf 100644 --- a/VCellMessaging/include/VCELL/SimulationMessaging.h +++ b/VCellMessaging/include/VCELL/SimulationMessaging.h @@ -33,10 +33,8 @@ static constexpr int Message_DEFAULT_PRIORITY = 0; //TODO: Re-write so that there is a messaging handler abstract class with two children: stdOut and CUrl class SimulationMessaging { public: - virtual ~SimulationMessaging() noexcept; - static void initialize(); - static SimulationMessaging* create(); static SimulationMessaging* getInstVar(); + static void cleanupInstanceVar(); void setWorkerEvent(JobEvent::Status status, const char *eventMessage); void setWorkerEvent(JobEvent::Status status, double progress, double timepoint); void setWorkerEvent(JobEvent::Status status, double progress, double timepoint, const char *eventMessage); @@ -54,11 +52,12 @@ class SimulationMessaging { } void waitUntilFinished(); #ifdef USE_MESSAGING - static SimulationMessaging* create(const char* broker, const char* smqusername, const char* passwd, const char* qname, const char* tname, - const char* vcusername, int simKey, int jobIndex, int taskID, int ttl_low=DEFAULT_TTL_LOW, int ttl_high=DEFAULT_TTL_HIGH); - friend void* startMessagingThread(void* param); + void initialize_curl_messaging(bool alsoPrintToStdOut, const char* broker, const char* vcusername, int simKey, int jobIndex, int taskID, int ttl_low=DEFAULT_TTL_LOW, int ttl_high=DEFAULT_TTL_HIGH); #endif + protected: + virtual ~SimulationMessaging() noexcept; + private: // Statics static void sendStdOutStatus(WorkerEvent*); @@ -78,9 +77,6 @@ class SimulationMessaging { static bool isInitialized; SimulationMessaging(); - #ifdef USE_MESSAGING - SimulationMessaging(const char* broker, const char* vcusername, int simKey, int jobIndex, int taskID, int ttl_low=DEFAULT_TTL_LOW, int ttl_high=DEFAULT_TTL_HIGH); - #endif void sendStatus(WorkerEvent*); void keepAlive(); diff --git a/VCellMessaging/src/MessageEventManager.cpp b/VCellMessaging/src/MessageEventManager.cpp index 5f9bedef7..100bbb1a9 100644 --- a/VCellMessaging/src/MessageEventManager.cpp +++ b/VCellMessaging/src/MessageEventManager.cpp @@ -10,10 +10,9 @@ MessageEventManager::MessageEventManager(std::function sendUpdateFunction): stopRequested{false}, - sendUpdateFunction{std::move(sendUpdateFunction)}, - eventQueueProcessingWorkerThread([this]() {this->performQueueProcessing();}) // Should auto-start thread + sendUpdateFunction{std::move(sendUpdateFunction)} { - + this->eventQueueProcessingWorkerThread = std::thread([this]() {this->performQueueProcessing();}); // Should auto-start thread } MessageEventManager::~MessageEventManager() { @@ -37,14 +36,16 @@ void MessageEventManager::requestStopAndWaitForIt() { std::unique_lock stopRequestedLock{this->stopRequestedMutex}; if (this->stopRequested) return; this->stopRequested = true; - this->needMessageProcessingForeman.notify_all(); - this->requestedStopForeman.wait(stopRequestedLock, [this]()->bool{return this->eventQueue.empty();}); + this->eventQueueForeman.notify_all(); // We want any sleeping workers to wake up, so they check if a stop was requested. + this->requestedStopForeman.wait(stopRequestedLock, [this]()->bool { + std::unique_lock queueLock{this->queuetex}; + return this->eventQueue.empty(); + }); } bool MessageEventManager::stopWasCalled() { std::unique_lock stopRequestedLock{this->stopRequestedMutex}; return this->stopRequested; - } /////////////////////////////////////////////////// @@ -66,35 +67,27 @@ void MessageEventManager::performQueueProcessing() { void MessageEventManager::processQueue() { while (true) { WorkerEvent* event; - {// START Clock-out Scope // - // The order of the locks is important! See Header File - std::unique_lock checkIfTheresWorkLock{this->timeClockMutex}; // This is to prevent loop processing while `enqueue` is being called + {// START Queuetex Scope // std::unique_lock shouldBeActiveLock(this->queuetex); if (this->eventQueue.empty()) { { // START stop-requested Scope std::unique_lock stopRequestedLock{this->stopRequestedMutex}; if (this->stopRequested) { - this->requestedStopForeman.notify_all(); - return; // We're all done; since this should be done on a jthread, this should also trigger a join. + this->requestedStopForeman.notify_all(); // Tell all stop-requesters that we've registered a stop. + return; // We're all done } } // END stop-requested Scope - checkIfTheresWorkLock.unlock(); // Need to allow enqueuing, can't do that with this locked, and we can't rescope but unique_lock order matters above! - // The worker can stop working, and "get some sleep" - // Wait for the worker to be "prodded" (via `needMessagingForeman.notify_one()`), AND the event queue is not empty - // Note that this `wait()` call, by design, unlocks the queue mutex, until it is "prodded". - this->needMessageProcessingForeman.wait(shouldBeActiveLock); - std::unique_lock stopRequestedLock{this->stopRequestedMutex}; + // If the code gets to here, the worker can stop working, and "get some sleep" + this->eventQueueForeman.wait(shouldBeActiveLock); // Note that this `wait()` call, by design, unlocks the queue mutex, until it is "prodded". if (this->eventQueue.empty()) continue; // Probably means we need to check if stop was requested again } - std::unique_lock stopRequestedLock{this->stopRequestedMutex}; event = this->eventQueue.front(); this->eventQueue.pop(); - }// END Clock-out Scope // + }// END Queuetex Scope // // Process Event this->processEvent(event); - // Remember to delete the event! We need the memory back! - delete event; + delete event; // Remember to delete the event! We need the memory back! } } @@ -103,19 +96,13 @@ void MessageEventManager::processEvent(WorkerEvent* event) { } void MessageEventManager::enqueue(WorkerEvent* event) { - // The order of the locks is important! See Header File - std::lock_guard workerIsActiveLock{this->timeClockMutex}; // Need to lock to prevent worker from "clocking out" while we set this up + std::lock_guard workerIsActiveLock{this->queuetex}; // Need to lock to prevent worker from checking if it has more work while we make the request. std::lock_guard stopRequestedLock{this->stopRequestedMutex}; // We scope this lock to the whole function; "last in the door" policy if (this->stopRequested) { std::cerr << "A new event was added to the messaging queue, but this queue has had `stop` requested!" << std::endl; delete event; return; // note: on this return function scope ends, and thus so too does out function-scoped locks } - - {// START Emplace Scope // - std::lock_guard queueLock{this->queuetex}; - this->eventQueue.emplace(event); - }// END Emplace Scope // - - this->needMessageProcessingForeman.notify_one(); // Nudges one sleeping worker awake. If the worker is awake...this does nothing; as it should. + this->eventQueue.emplace(event); + this->eventQueueForeman.notify_one(); // Nudges one sleeping worker awake. If the worker is awake...this does nothing; as it should. } \ No newline at end of file diff --git a/VCellMessaging/src/SimulationMessaging.cpp b/VCellMessaging/src/SimulationMessaging.cpp index 5dea90ff4..45e04a982 100644 --- a/VCellMessaging/src/SimulationMessaging.cpp +++ b/VCellMessaging/src/SimulationMessaging.cpp @@ -4,50 +4,37 @@ #include #include - - -static const double WORKEREVENT_MESSAGE_MIN_TIME_SECONDS = 15.0; +static constexpr double WORKEREVENT_MESSAGE_MIN_TIME_SECONDS = 15.0; bool SimulationMessaging::isInitialized = false; -SimulationMessaging *SimulationMessaging::m_inst = NULL; +SimulationMessaging *SimulationMessaging::m_inst = nullptr; SimulationMessaging::SimulationMessaging(): eventHandler(std::bind(&SimulationMessaging::sendStatus, this, std::placeholders::_1)), + workerEventOutputMode{WORKEREVENT_OUTPUT_MODE_STDOUT}, taskID{-1}, - m_jobIndex{-1} -{ - this->taskID = -1; - this->workerEventOutputMode = WORKEREVENT_OUTPUT_MODE_STDOUT; - this->curlHandler = new NullCurlProxy(); - -} - -#ifdef USE_MESSAGING -SimulationMessaging::SimulationMessaging(const char* broker, const char* vcusername, int simKey, int jobIndex, int taskID, int ttl_low, int ttl_high): - eventHandler(std::bind(&SimulationMessaging::sendStatus, this, std::placeholders::_1)), - workerEventOutputMode{WORKEREVENT_OUTPUT_MODE_MESSAGING}, - taskID{taskID}, - m_jobIndex{jobIndex}, + m_jobIndex{-1}, bNewWorkerEvent{false} { - this->curlHandler = new CurlProxy(simKey, taskID, jobIndex, vcusername, broker, ttl_low, ttl_high); + this->curlHandler = new NullCurlProxy(); time(&this->lastSentEventTime); } -#endif SimulationMessaging::~SimulationMessaging() noexcept{ delete this->curlHandler; } SimulationMessaging* SimulationMessaging::getInstVar() { + if (nullptr == SimulationMessaging::m_inst) SimulationMessaging::m_inst = new SimulationMessaging(); return SimulationMessaging::m_inst; } -SimulationMessaging* SimulationMessaging::create(){ - if (SimulationMessaging::m_inst == NULL) SimulationMessaging::m_inst = new SimulationMessaging(); - - return SimulationMessaging::m_inst; +void SimulationMessaging::cleanupInstanceVar() { + if (nullptr == SimulationMessaging::m_inst) return; + SimulationMessaging::m_inst->waitUntilFinished(); + delete SimulationMessaging::m_inst; + SimulationMessaging::m_inst = nullptr; } void SimulationMessaging::sendStatus(WorkerEvent* event) { @@ -132,26 +119,16 @@ void SimulationMessaging::setWorkerEvent(JobEvent::Status status, const double p void SimulationMessaging::waitUntilFinished() { this->eventHandler.requestStopAndWaitForIt(); - // if (workerEventOutputMode == WORKEREVENT_OUTPUT_MODE_STDOUT) return; - // #ifdef USE_MESSAGING - // std::cout << "!!!waiting for thread to exit" << std::endl; - // pthread_join(newWorkerEventThread, NULL); - // std::cout << "!!Threads joined successfully" << std::endl; - // #endif } #ifdef USE_MESSAGING -SimulationMessaging* SimulationMessaging::create(const char* broker, const char* smqusername, const char* passwd, const char* qname, const char* tname, const char* vcusername, int simKey, int jobIndex, int taskID, int ttl_low, int ttl_high){ - if (m_inst != NULL && m_inst->workerEventOutputMode == WORKEREVENT_OUTPUT_MODE_STDOUT) { - delete m_inst; - m_inst = NULL; - } - if (m_inst == NULL){ - m_inst = new SimulationMessaging(broker, vcusername, simKey, jobIndex, taskID, ttl_low, ttl_high); - } - - return(m_inst); +void SimulationMessaging::initialize_curl_messaging(bool alsoPrintToStdOut, const char* broker, const char* vcusername, int simKey, int jobIndex, int givenTaskID, int ttl_low, int ttl_high){ + this->workerEventOutputMode = alsoPrintToStdOut ? WORKEREVENT_OUTPUT_MODE_ALL : WORKEREVENT_OUTPUT_MODE_MESSAGING; + this->taskID = givenTaskID; + this->m_jobIndex = jobIndex; + delete this->curlHandler; // get rid of the null one we make by default + this->curlHandler = new CurlProxy(simKey, taskID, jobIndex, vcusername, broker, ttl_low, ttl_high); } // void SimulationMessaging::start() { From f1f1cb72fdec208dd2a5b5ead33aec96d9345c62 Mon Sep 17 00:00:00 2001 From: Logan Drescher Date: Wed, 17 Dec 2025 13:54:22 -0500 Subject: [PATCH 15/34] rearranged some test code to use same body --- tests/unit/message_processing_test.cpp | 86 +++++++++++++++++--------- tests/unit/smoke_test.cpp | 44 ++++++------- 2 files changed, 76 insertions(+), 54 deletions(-) diff --git a/tests/unit/message_processing_test.cpp b/tests/unit/message_processing_test.cpp index 548c83738..b8840d567 100644 --- a/tests/unit/message_processing_test.cpp +++ b/tests/unit/message_processing_test.cpp @@ -3,53 +3,81 @@ // #include #include -#include -#include +// #include +// #include +#include +#include #include #include #include "VCellSundialsSolver.h" static std::vector eventTracker; -static std::mutex eventTrackerMutex; + void appendNextIndex(const WorkerEvent* event); std::vector generateFibonacci(int length); long sequenceComparison(const std::vector &sequence1, const std::vector &sequence2); -TEST(MessageProcessingTest, MessagesAreProcessed) { +static std::unordered_map nameMap; + +// std::mutex& getMeyersMutex() { +// static std::mutex eventMutex; +// return eventMutex; +// } - MessageEventManager eventManager{[](WorkerEvent* event)->void{appendNextIndex(event);}}; - const int NUM_ITERATIONS = 100; - for (int i = 0; i < NUM_ITERATIONS; ++i) { - JobEvent::Status status; - switch (i + 1) { - case 1: status = JobEvent::JOB_STARTING; break; - case NUM_ITERATIONS: status = JobEvent::JOB_COMPLETED; break; - default: status = JobEvent::JOB_PROGRESS; break; +TEST(MessageProcessingTest, MessagesAreProcessed) { + try { + nameMap[std::this_thread::get_id()] = "Main Thread"; + MessageEventManager eventManager{[](WorkerEvent* event)->void{appendNextIndex(event);}}; + const int NUM_ITERATIONS = 100; + for (int i = 0; i < NUM_ITERATIONS; ++i) { + //std::cerr << "Main PID = " << getpid() << "\n"; + JobEvent::Status status; + switch (i + 1) { + case 1: status = JobEvent::JOB_STARTING; break; + case NUM_ITERATIONS: status = JobEvent::JOB_COMPLETED; break; + default: status = JobEvent::JOB_PROGRESS; break; + } + //std::cerr << "Locking mutex at " << &getMeyersMutex << " on thread " << nameMap[std::this_thread::get_id()] << "\n"; + // getMeyersMutex().lock();//eventTrackerMutex->lock(); + //std::cerr << "Obtained mutex at " << &getMeyersMutex << " on thread " << nameMap[std::this_thread::get_id()] << "\n"; + eventManager.enqueue(status, (i + 1) / 100.0, i, std::to_string(i).c_str()); + //std::cerr << "Unlocking mutex at " << &getMeyersMutex << " on thread " << nameMap[std::this_thread::get_id()] << "\n"; + // getMeyersMutex().unlock();//eventTrackerMutex->unlock(); } - eventTrackerMutex.lock(); - eventManager.enqueue(status, (i + 1) / 100.0, i, std::to_string(i).c_str()); - eventTrackerMutex.unlock(); + eventManager.requestStopAndWaitForIt(); + std::vector expectedResults = generateFibonacci(NUM_ITERATIONS); + // delete eventTrackerMutex; + ASSERT_TRUE(!sequenceComparison(eventTracker, expectedResults)); + } catch (const std::exception& e) { + std::cerr << "Caught exception in main test body: " << e.what() << std::endl; } - eventManager.requestStopAndWaitForIt(); - std::vector expectedResults = generateFibonacci(NUM_ITERATIONS); - ASSERT_TRUE(!sequenceComparison(eventTracker, expectedResults)); } void appendNextIndex(const WorkerEvent* event) { - eventTrackerMutex.lock(); - if (eventTracker.empty()) { - eventTracker.emplace_back("0"); - } else if (1 == eventTracker.size()) { - eventTracker.emplace_back("1"); - } else { - const long firstValue = std::stol(eventTracker[eventTracker.size() - 2]); - const long secondValue = std::stol(eventTracker[eventTracker.size() - 1]); - const std::string nextValue{std::to_string(firstValue + secondValue)}; - eventTracker.push_back(nextValue); + //std::cerr << "Event PID = " << getpid() << "\n"; + if (!nameMap.contains(std::this_thread::get_id())) nameMap[std::this_thread::get_id()] = "Event Thread"; + try { + //std::cerr << "Locking mutex at " << &getMeyersMutex << " on thread " << nameMap[std::this_thread::get_id()] << "\n"; + // getMeyersMutex().lock();//eventTrackerMutex->lock(); + //std::cerr << "Obtained mutex at " << &getMeyersMutex << " on thread " << nameMap[std::this_thread::get_id()] << "\n"; + if (eventTracker.empty()) { + eventTracker.emplace_back("0"); + } else if (1 == eventTracker.size()) { + eventTracker.emplace_back("1"); + } else { + const long firstValue = std::stol(eventTracker[eventTracker.size() - 2]); + const long secondValue = std::stol(eventTracker[eventTracker.size() - 1]); + const std::string nextValue{std::to_string(firstValue + secondValue)}; + eventTracker.push_back(nextValue); + } + //std::cerr << "Unlocking mutex at " << &getMeyersMutex << " on thread " << nameMap[std::this_thread::get_id()] << "\n"; + // getMeyersMutex().unlock();//eventTrackerMutex->unlock(); + } catch (const std::exception& e) { + std::cerr << "Caught exception in event loop: " << e.what() << std::endl; + exit(2); } - eventTrackerMutex.unlock(); } std::vector generateFibonacci(const int length) { diff --git a/tests/unit/smoke_test.cpp b/tests/unit/smoke_test.cpp index 6a6f66893..18d94e1ee 100644 --- a/tests/unit/smoke_test.cpp +++ b/tests/unit/smoke_test.cpp @@ -16,8 +16,7 @@ void compare(const std::filesystem::path& file1, const std::filesystem::path& file2, float tolerance); -TEST(SmokeTest, UserProvidesFilesWithoutJMS) { - constexpr int taskID = -1, hashID = 1489333437; +void performSmokeTest(const int taskID, const int hashID) { const std::filesystem::path RESOURCE_DIRECTORY{RESOURCE_DIR}; const std::filesystem::path OUTPUT_TARGET{RESOURCE_DIRECTORY /std::format("SimID_{}_0_.ida", hashID)}; const std::array NECESSARY_FILES{ @@ -36,36 +35,31 @@ TEST(SmokeTest, UserProvidesFilesWithoutJMS) { throw std::runtime_error("Could not open output file[" + OUTPUT_TARGET.string() + "] for writing."); } - activateSolver(inputFileStream, outputFile, taskID); + try { + activateSolver(inputFileStream, outputFile, taskID); + } catch (const std::exception& e) { + std::cerr << "Error caught in test: " << e.what() << std::endl; + exit(EXIT_FAILURE); + } fclose(outputFile); compare(OUTPUT_TARGET, NECESSARY_FILES[1], 1e-7); } -TEST(SmokeTest, UserProvidesFilesWithJMS) { - constexpr int taskID = 2025, hashID = 256118677; - const std::filesystem::path RESOURCE_DIRECTORY{RESOURCE_DIR}; - const std::filesystem::path OUTPUT_TARGET{RESOURCE_DIRECTORY /std::format("SimID_{}_0_.ida", hashID)}; - const std::array NECESSARY_FILES{ - RESOURCE_DIRECTORY /std::format("SimID_{}_0_.cvodeInput", hashID), - RESOURCE_DIRECTORY /std::format("SimID_{}_0_.ida.expected", hashID) - }; - for (const auto& file : NECESSARY_FILES) { - assert(std::filesystem::exists(file)); - } - FILE *outputFile = NULL; - std::ifstream inputFileStream{NECESSARY_FILES[0]}; - if (!inputFileStream.is_open()) { throw std::runtime_error("input file [" + NECESSARY_FILES[0].string() + "] doesn't exit!"); } - - // Open the output file... - if (NULL == (outputFile = fopen(OUTPUT_TARGET.string().c_str(), "w"))) { - throw std::runtime_error("Could not open output file[" + OUTPUT_TARGET.string() + "] for writing."); - } +TEST(SmokeTest, UserProvidesFilesWithoutJMS) { + constexpr int taskID = -1, hashID = 1489333437; + performSmokeTest(taskID, hashID); +} - activateSolver(inputFileStream, outputFile, taskID); - fclose(outputFile); +TEST(SmokeTest, UserProvidesFilesWithJMS) { + constexpr int taskID = 2025; + #ifdef TEST_WITH_LOCALHOST + constexpr int hashID = 886118677; + #else + constexpr int hashID = 256118677; + #endif - compare(OUTPUT_TARGET, NECESSARY_FILES[1], 1e-7); + performSmokeTest(taskID, hashID); } void compare(const std::filesystem::path& file1, const std::filesystem::path& file2, float tolerance) { From 1a306e581fdb52b799b4eceb8afa4a74dc8cc8af Mon Sep 17 00:00:00 2001 From: Logan Drescher Date: Wed, 17 Dec 2025 13:54:39 -0500 Subject: [PATCH 16/34] Fixed undefined behavior bug --- IDAWin/SundialsSolverInterface.cpp | 5 +---- IDAWin/VCellSolverFactory.cpp | 14 +++++--------- IDAWin/VCellSundialsSolver.cpp | 4 +++- tests/unit/message_processing_test.cpp | 24 ------------------------ 4 files changed, 9 insertions(+), 38 deletions(-) diff --git a/IDAWin/SundialsSolverInterface.cpp b/IDAWin/SundialsSolverInterface.cpp index 09fcebcb8..774fa2b31 100644 --- a/IDAWin/SundialsSolverInterface.cpp +++ b/IDAWin/SundialsSolverInterface.cpp @@ -79,10 +79,7 @@ void activateSolver(std::ifstream& inputFileStream, FILE* outputFile, int taskID } // cleanup - if (SimulationMessaging::getInstVar() != nullptr) { - SimulationMessaging::getInstVar()->waitUntilFinished(); - delete SimulationMessaging::getInstVar(); - } + SimulationMessaging::cleanupInstanceVar(); if (!errorMsg.empty()) errExit(returnCode, errorMsg); } diff --git a/IDAWin/VCellSolverFactory.cpp b/IDAWin/VCellSolverFactory.cpp index 212508f2e..648cd9300 100644 --- a/IDAWin/VCellSolverFactory.cpp +++ b/IDAWin/VCellSolverFactory.cpp @@ -109,12 +109,6 @@ VCellSolverInputBreakdown VCellSolverFactory::parseInputFile(std::ifstream& inpu else throw VCell::Exception("Unexpected token \"" + nextToken + "\" in the input file!"); } - - - #ifdef USE_MESSAGING - // Since messaging assumes we have a job requiring messaging, we should initialize a "default" messaging handler - if (NULL == SimulationMessaging::getInstVar()) SimulationMessaging::create(); - #endif return inputBreakdown; } @@ -469,8 +463,10 @@ static void loadJMSInfo(std::istream &ifsInput, int taskID) { } #ifdef USE_MESSAGING - SimulationMessaging::create(broker.c_str(), smqUserName.c_str(), - password.c_str(), qName.c_str(), topicName.c_str(), - vCellUsername.c_str(), simKey, jobIndex, taskID); + // SimulationMessaging::getInstVar()->initialize_curl_messaging(broker.c_str(), smqUserName.c_str(), + // password.c_str(), qName.c_str(), topicName.c_str(), + // vCellUsername.c_str(), simKey, jobIndex, taskID); + SimulationMessaging::getInstVar()->initialize_curl_messaging(false, broker.c_str(), + vCellUsername.c_str(), simKey, jobIndex, taskID); #endif } diff --git a/IDAWin/VCellSundialsSolver.cpp b/IDAWin/VCellSundialsSolver.cpp index 3ff290601..168793ad0 100644 --- a/IDAWin/VCellSundialsSolver.cpp +++ b/IDAWin/VCellSundialsSolver.cpp @@ -613,7 +613,9 @@ double VCellSundialsSolver::getNextEventTime() { void VCellSundialsSolver::checkStopRequested(double time, long numIterations) { #ifdef USE_MESSAGING - if (SimulationMessaging::getInstVar()->isStopRequested()) { throw StoppedByUserException("stopped by user"); } + auto instVar = SimulationMessaging::getInstVar(); + if (nullptr == instVar) return; + if (instVar->isStopRequested()) { throw StoppedByUserException("stopped by user"); } #endif } diff --git a/tests/unit/message_processing_test.cpp b/tests/unit/message_processing_test.cpp index b8840d567..5b7463cf4 100644 --- a/tests/unit/message_processing_test.cpp +++ b/tests/unit/message_processing_test.cpp @@ -3,10 +3,7 @@ // #include #include -// #include -// #include #include -#include #include #include @@ -14,37 +11,23 @@ static std::vector eventTracker; - void appendNextIndex(const WorkerEvent* event); std::vector generateFibonacci(int length); long sequenceComparison(const std::vector &sequence1, const std::vector &sequence2); -static std::unordered_map nameMap; - -// std::mutex& getMeyersMutex() { -// static std::mutex eventMutex; -// return eventMutex; -// } TEST(MessageProcessingTest, MessagesAreProcessed) { try { - nameMap[std::this_thread::get_id()] = "Main Thread"; MessageEventManager eventManager{[](WorkerEvent* event)->void{appendNextIndex(event);}}; const int NUM_ITERATIONS = 100; for (int i = 0; i < NUM_ITERATIONS; ++i) { - //std::cerr << "Main PID = " << getpid() << "\n"; JobEvent::Status status; switch (i + 1) { case 1: status = JobEvent::JOB_STARTING; break; case NUM_ITERATIONS: status = JobEvent::JOB_COMPLETED; break; default: status = JobEvent::JOB_PROGRESS; break; } - //std::cerr << "Locking mutex at " << &getMeyersMutex << " on thread " << nameMap[std::this_thread::get_id()] << "\n"; - // getMeyersMutex().lock();//eventTrackerMutex->lock(); - //std::cerr << "Obtained mutex at " << &getMeyersMutex << " on thread " << nameMap[std::this_thread::get_id()] << "\n"; eventManager.enqueue(status, (i + 1) / 100.0, i, std::to_string(i).c_str()); - //std::cerr << "Unlocking mutex at " << &getMeyersMutex << " on thread " << nameMap[std::this_thread::get_id()] << "\n"; - // getMeyersMutex().unlock();//eventTrackerMutex->unlock(); } eventManager.requestStopAndWaitForIt(); std::vector expectedResults = generateFibonacci(NUM_ITERATIONS); @@ -56,12 +39,7 @@ TEST(MessageProcessingTest, MessagesAreProcessed) { } void appendNextIndex(const WorkerEvent* event) { - //std::cerr << "Event PID = " << getpid() << "\n"; - if (!nameMap.contains(std::this_thread::get_id())) nameMap[std::this_thread::get_id()] = "Event Thread"; try { - //std::cerr << "Locking mutex at " << &getMeyersMutex << " on thread " << nameMap[std::this_thread::get_id()] << "\n"; - // getMeyersMutex().lock();//eventTrackerMutex->lock(); - //std::cerr << "Obtained mutex at " << &getMeyersMutex << " on thread " << nameMap[std::this_thread::get_id()] << "\n"; if (eventTracker.empty()) { eventTracker.emplace_back("0"); } else if (1 == eventTracker.size()) { @@ -72,8 +50,6 @@ void appendNextIndex(const WorkerEvent* event) { const std::string nextValue{std::to_string(firstValue + secondValue)}; eventTracker.push_back(nextValue); } - //std::cerr << "Unlocking mutex at " << &getMeyersMutex << " on thread " << nameMap[std::this_thread::get_id()] << "\n"; - // getMeyersMutex().unlock();//eventTrackerMutex->unlock(); } catch (const std::exception& e) { std::cerr << "Caught exception in event loop: " << e.what() << std::endl; exit(2); From dd2eddfb64e5ea04f33abe1fe724b982707bac36 Mon Sep 17 00:00:00 2001 From: Logan Drescher Date: Thu, 13 Nov 2025 12:18:37 -0500 Subject: [PATCH 17/34] trying libcurl downgrade --- .github/workflows/cd.yml | 6 ++++-- ExpressionParser/ExpressionParserTokenManager.cpp | 1 + ExpressionParser/ExpressionParserTokenManager.h | 1 - IDAWin/VCellIDASolver.cpp | 1 + IDAWin/VCellSundialsSolver.cpp | 2 ++ 5 files changed, 8 insertions(+), 3 deletions(-) diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index 855bcab39..f59ca393a 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -54,7 +54,9 @@ jobs: if: matrix.platform == 'macos-15-intel' shell: bash run: | - brew install conan + wget https://github.com/conan-io/conan/releases/download/2.22.2/conan-2.22.2-macos-x86_64.tgz + tar -xvzf conan-2.22.2-macos-x86_64.tgz + cp ./bin/conan /usr/local/bin conan --version mkdir -p ~/.conan2/profiles/ touch ~/.conan2/profiles/default # if we don't make a file first, cp thinks its a folder that doesn't exist @@ -115,7 +117,7 @@ jobs: sudo apt install mold libc++-dev libc++abi-dev sudo rm /usr/bin/ld sudo ln -s $(which mold) /usr/bin/ld - conan install . --output-folder build --build=missing + conan install -v trace . --output-folder build --build=missing - name: Install Dependencies through Conan on Linux if: matrix.platform == 'ubuntu-24.04-arm' diff --git a/ExpressionParser/ExpressionParserTokenManager.cpp b/ExpressionParser/ExpressionParserTokenManager.cpp index 7873eaa5e..52260642b 100644 --- a/ExpressionParser/ExpressionParserTokenManager.cpp +++ b/ExpressionParser/ExpressionParserTokenManager.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include "ExpressionParserTokenManager.h" #include "RuntimeException.h" diff --git a/ExpressionParser/ExpressionParserTokenManager.h b/ExpressionParser/ExpressionParserTokenManager.h index 1c4bec14d..ea461ccfa 100644 --- a/ExpressionParser/ExpressionParserTokenManager.h +++ b/ExpressionParser/ExpressionParserTokenManager.h @@ -5,7 +5,6 @@ #include - #include "Token.h" #include "SimpleCharStream.h" diff --git a/IDAWin/VCellIDASolver.cpp b/IDAWin/VCellIDASolver.cpp index 70c70bf2b..28a3decc6 100644 --- a/IDAWin/VCellIDASolver.cpp +++ b/IDAWin/VCellIDASolver.cpp @@ -16,6 +16,7 @@ //#include #include #include +#include #ifdef USE_MESSAGING #include #endif diff --git a/IDAWin/VCellSundialsSolver.cpp b/IDAWin/VCellSundialsSolver.cpp index 168793ad0..81dd8d483 100644 --- a/IDAWin/VCellSundialsSolver.cpp +++ b/IDAWin/VCellSundialsSolver.cpp @@ -7,6 +7,8 @@ #include #include #include +#include +#include #ifdef USE_MESSAGING #include From 3a0cdb309496789303674e3cbfc58dfb00b651ef Mon Sep 17 00:00:00 2001 From: Logan Drescher Date: Thu, 13 Nov 2025 14:56:00 -0500 Subject: [PATCH 18/34] changing intel-mac conan installation to python --- .github/workflows/cd.yml | 4 +--- ExpressionParser/ASTFuncNode.cpp | 3 +-- conan-profiles/CI-CD/MacOS-AMD64_profile.txt | 2 +- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index f59ca393a..46e1b5dc8 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -54,9 +54,7 @@ jobs: if: matrix.platform == 'macos-15-intel' shell: bash run: | - wget https://github.com/conan-io/conan/releases/download/2.22.2/conan-2.22.2-macos-x86_64.tgz - tar -xvzf conan-2.22.2-macos-x86_64.tgz - cp ./bin/conan /usr/local/bin + python3 -m pip install conan conan --version mkdir -p ~/.conan2/profiles/ touch ~/.conan2/profiles/default # if we don't make a file first, cp thinks its a folder that doesn't exist diff --git a/ExpressionParser/ASTFuncNode.cpp b/ExpressionParser/ASTFuncNode.cpp index 59bf3a0e6..75669e157 100644 --- a/ExpressionParser/ASTFuncNode.cpp +++ b/ExpressionParser/ASTFuncNode.cpp @@ -2,10 +2,9 @@ #include #include - +#include #include "ASTFuncNode.h" -#include #include "RuntimeException.h" #include "ExpressionException.h" #include "MathUtil.h" diff --git a/conan-profiles/CI-CD/MacOS-AMD64_profile.txt b/conan-profiles/CI-CD/MacOS-AMD64_profile.txt index bc4007fdf..cfb9c0c52 100644 --- a/conan-profiles/CI-CD/MacOS-AMD64_profile.txt +++ b/conan-profiles/CI-CD/MacOS-AMD64_profile.txt @@ -4,5 +4,5 @@ build_type=Release compiler=apple-clang compiler.cppstd=20 compiler.libcxx=libc++ -compiler.version=17 +compiler.version=15 os=Macos \ No newline at end of file From 2ded3e3cf5ec8da9786235eda352930eb9c404d7 Mon Sep 17 00:00:00 2001 From: Logan Drescher Date: Thu, 13 Nov 2025 15:29:03 -0500 Subject: [PATCH 19/34] intel mac 13 too old; upgrading to MacOS15! --- .github/workflows/cd.yml | 4 ++-- conan-profiles/CI-CD/MacOS-AMD64_profile.txt | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index 46e1b5dc8..3c237162d 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -54,7 +54,7 @@ jobs: if: matrix.platform == 'macos-15-intel' shell: bash run: | - python3 -m pip install conan + brew install conan conan --version mkdir -p ~/.conan2/profiles/ touch ~/.conan2/profiles/default # if we don't make a file first, cp thinks its a folder that doesn't exist @@ -139,7 +139,7 @@ jobs: cmake -B . -S .. -G "Ninja" -DCMAKE_TOOLCHAIN_FILE="conan_toolchain.cmake" -DCMAKE_BUILD_TYPE=Release cmake --build . --config Release - - name: Build Unix + - name: Build Non-Intel-Mac Unix if: matrix.platform == 'ubuntu-latest' || matrix.platform == 'ubuntu-24.04-arm' || matrix.platform == 'macos-15-intel' || matrix.platform == 'macos-latest' run: | echo "working dir is $PWD" diff --git a/conan-profiles/CI-CD/MacOS-AMD64_profile.txt b/conan-profiles/CI-CD/MacOS-AMD64_profile.txt index cfb9c0c52..bc4007fdf 100644 --- a/conan-profiles/CI-CD/MacOS-AMD64_profile.txt +++ b/conan-profiles/CI-CD/MacOS-AMD64_profile.txt @@ -4,5 +4,5 @@ build_type=Release compiler=apple-clang compiler.cppstd=20 compiler.libcxx=libc++ -compiler.version=15 +compiler.version=17 os=Macos \ No newline at end of file From c2ffa5dbc890e88b3ce7c1d62ddf378c7f829ea8 Mon Sep 17 00:00:00 2001 From: Logan Drescher Date: Tue, 9 Dec 2025 13:11:37 -0500 Subject: [PATCH 20/34] Updating build configurations --- .gitignore | 28 +++++++++---------- CMakeLists.txt | 24 ++++++---------- .../include/VCELL/CurlProxyClasses.h | 4 +-- 3 files changed, 23 insertions(+), 33 deletions(-) diff --git a/.gitignore b/.gitignore index 6aa1b85b4..4792b8fbe 100644 --- a/.gitignore +++ b/.gitignore @@ -1,31 +1,29 @@ -/build-linux64-server/ -/build-dockcross-win64/ + /.project /.cproject -/build-win64/ /.DS_Store -/build-linux64/ -/build-macos/ /packages/ -/build_archive/ -/build-linux64-ubuntu/ + /conan/ /singularity/ -/cmake-build* -/build* -/debug-build* /.idea/ -/nfsim/ -/all_solvers/* .DS_Store -NFsim_v1.11/tests/smoke/SimID_273069657_0_.gdat -NFsim_v1.11/tests/smoke/SimID_273069657_0_.species - *.ida CMakeUserPresets.json + +/build-linux64-server/ +/build-dockcross-win64/ +/build-win64/ +/build-linux64/ +/build-macos/ +/build_archive/ +/build-linux64-ubuntu/ +/build* +/cmake-build* +/debug-build* conan-build conan-build/* conan_provider.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index 887bcec7a..c45fc2750 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -19,7 +19,7 @@ if(POLICY CMP0057) cmake_policy(SET CMP0057 NEW) endif() -if (WINDOWS) +if (WIN32) cmake_policy(SET CMP0091 NEW) endif () @@ -89,7 +89,6 @@ endif() option(OPTION_TARGET_MESSAGING "Messaging (requires libcurl)" off) option(OPTION_TARGET_DOCS "Generate Doxygen documentation" off) - if (${OPTION_TARGET_DOCS}) # if (DOXYGEN_FOUND AND ${CMAKE_VERSION} GREATER_EQUAL 3.9) @@ -117,34 +116,27 @@ endif() set(HDF5_BUILD_CPP_LIB OFF CACHE BOOL "" ) -set(LINUX FALSE) -if (${CMAKE_SYSTEM_NAME} MATCHES Linux) - set(LINUX TRUE) -endif() -set(WINDOWS FALSE) -if (${CMAKE_SYSTEM_NAME} MATCHES Windows) - set(WINDOWS TRUE) - set(WIN32 TRUE) - set(MINGW TRUE) -endif() +#if (WIN32) # Only linux is designed at this point to support messaging, but unix should work too (and our devs generally work on macs) +# set(OPTION_TARGET_MESSAGING OFF) +#endif() set (ARCH_64bit FALSE) if (CMAKE_SIZEOF_VOID_P EQUAL 8) set (ARCH_64bit TRUE) endif() -if (NOT APPLE AND NOT LINUX AND NOT WINDOWS) +if (NOT APPLE AND NOT LINUX AND NOT WIN32) message(FATAL_ERROR "Unsupported Operating System") endif() #--------------------------- # IDE SUPPORT #--------------------------- -if (WINDOWS) +if (WIN32) set(NETBEANS_WINDOWS TRUE) set(CMAKE_RC_FLAGS "-DGCC_WINDRES") -endif(WINDOWS) +endif(WIN32) #add_definitions(-DFORTRAN_UNDERSCORE) @@ -220,7 +212,7 @@ if (OPTION_EXTRA_CONFIG_INFO) include(CMakePrintHelpers) cmake_print_variables(OPTION_TARGET_MESSAGING OPTION_TARGET_DOCS) cmake_print_variables(CMAKE_CXX_FLAGS CMAKE_C_FLAGS) - cmake_print_variables(CMAKE_SYSTEM_NAME WINDOWS WIN32 MINGW APPLE ARCH_64bit ARCH_32bit) + cmake_print_variables(CMAKE_SYSTEM_NAME WIN32 APPLE LINUX ARCH_64bit ARCH_32bit) cmake_print_variables(CMAKE_CPP_COMPILER CMAKE_C_COMPILER CMAKE_CXX_COMPILER) endif () diff --git a/VCellMessaging/include/VCELL/CurlProxyClasses.h b/VCellMessaging/include/VCELL/CurlProxyClasses.h index 54de41acc..450a7eec9 100644 --- a/VCellMessaging/include/VCELL/CurlProxyClasses.h +++ b/VCellMessaging/include/VCELL/CurlProxyClasses.h @@ -4,8 +4,6 @@ #ifndef VCELL_ODE_NUMERICS_CURLPROXY_H #define VCELL_ODE_NUMERICS_CURLPROXY_H #include -#include - #include "VCELL/WorkerEvent.h" @@ -26,6 +24,8 @@ class NullCurlProxy final : public AbstractCurlProxy { }; #ifdef USE_MESSAGING +#include + class CurlProxy final : public AbstractCurlProxy { public: CurlProxy(long simKey, int taskID, int jobIndex, const std::string& vcusername, const std::string& broker, int ttlLow, int ttlHigh); From 0802000ba2a06f367df03ef5c8d7af632983d503 Mon Sep 17 00:00:00 2001 From: Logan Drescher Date: Tue, 9 Dec 2025 15:00:49 -0500 Subject: [PATCH 21/34] Added build argument to select whether to test with localhost or not --- .github/workflows/docker-deploy.yml | 6 + CMakeLists.txt | 5 + VCellMessaging/src/CurlProxyClasses.cpp | 1 + tests/CMakeLists.txt | 9 +- .../resources/SimID_886118677_0_.cvodeInput | 24 +++ .../resources/SimID_886118677_0_.functions | 6 + .../resources/SimID_886118677_0_.ida.expected | 159 ++++++++++++++++++ tests/unit/resources/SimID_886118677_0_.log | 4 + tests/unit/smoke_test.cpp | 7 +- 9 files changed, 212 insertions(+), 9 deletions(-) create mode 100644 tests/unit/resources/SimID_886118677_0_.cvodeInput create mode 100644 tests/unit/resources/SimID_886118677_0_.functions create mode 100644 tests/unit/resources/SimID_886118677_0_.ida.expected create mode 100644 tests/unit/resources/SimID_886118677_0_.log diff --git a/.github/workflows/docker-deploy.yml b/.github/workflows/docker-deploy.yml index dda5774d5..db2f16f66 100644 --- a/.github/workflows/docker-deploy.yml +++ b/.github/workflows/docker-deploy.yml @@ -9,6 +9,12 @@ env: jobs: build-and-push-image: runs-on: ubuntu-latest + services: + activemq: + image: rmohr/activemq:latest # Or a specific version like rmohr/activemq:5.15.9 + ports: + - 30163:30163 # JMS port + - 8161:8161 # Web console port permissions: contents: read packages: write diff --git a/CMakeLists.txt b/CMakeLists.txt index c45fc2750..1be1bd6bf 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -89,6 +89,7 @@ endif() option(OPTION_TARGET_MESSAGING "Messaging (requires libcurl)" off) option(OPTION_TARGET_DOCS "Generate Doxygen documentation" off) +option(OPTION_TEST_WITH_LOCALHOST "Use localhost as server rather than vcell.cam.uchc.edu" off) if (${OPTION_TARGET_DOCS}) # if (DOXYGEN_FOUND AND ${CMAKE_VERSION} GREATER_EQUAL 3.9) @@ -166,6 +167,10 @@ if (OPTION_TARGET_MESSAGING) add_definitions(-DUSE_MESSAGING) endif() +if (OPTION_TEST_WITH_LOCALHOST) + add_definitions(-DTEST_WITH_LOCALHOST) +endif() + ###################################### # # Add subdirectories diff --git a/VCellMessaging/src/CurlProxyClasses.cpp b/VCellMessaging/src/CurlProxyClasses.cpp index 78f07f2ae..8bd1a591a 100644 --- a/VCellMessaging/src/CurlProxyClasses.cpp +++ b/VCellMessaging/src/CurlProxyClasses.cpp @@ -21,6 +21,7 @@ std::string trim(const std::string& str) { #include #include #include +#include #include "VCELL/JobEventStatus.h" #include "VCELL/SimulationMessaging.h" diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 9ee8867de..824aacb0b 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -27,9 +27,12 @@ target_link_libraries(unit_tests PRIVATE ) gtest_discover_tests(unit_tests) -target_compile_definitions(unit_tests PRIVATE - RESOURCE_DIR="${CMAKE_CURRENT_SOURCE_DIR}/unit/resources" -) +target_compile_definitions(unit_tests PRIVATE RESOURCE_DIR="${CMAKE_CURRENT_SOURCE_DIR}/unit/resources") +if (OPTION_TEST_WITH_LOCALHOST) + target_compile_definitions(unit_tests PRIVATE TEST_WITH_LOCALHOST=1) +else() + target_compile_definitions(unit_tests PRIVATE TEST_WITH_LOCALHOST=0) +endif () #add_executable(integration_tests diff --git a/tests/unit/resources/SimID_886118677_0_.cvodeInput b/tests/unit/resources/SimID_886118677_0_.cvodeInput new file mode 100644 index 000000000..61ee66db3 --- /dev/null +++ b/tests/unit/resources/SimID_886118677_0_.cvodeInput @@ -0,0 +1,24 @@ +# JMS_Paramters +JMS_PARAM_BEGIN +JMS_BROKER localhost:30163 +JMS_USER clientUser dummy +JMS_QUEUE workerEvent +JMS_TOPIC serviceControl +VCELL_USER ldresche +SIMULATION_KEY 886118677 +JOB_INDEX 0 +JMS_PARAM_END + +SOLVER CVODE +STARTING_TIME 0.0 +ENDING_TIME 10.0 +RELATIVE_TOLERANCE 1.0E-9 +ABSOLUTE_TOLERANCE 1.0E-9 +MAX_TIME_STEP 1.0 +KEEP_EVERY 1 +NUM_EQUATIONS 2 +ODE s0 INIT 1.0; + RATE - ((0.5 * s0) - (0.2 * s1)); +ODE s1 INIT 0.0; + RATE ((0.5 * s0) - (0.2 * s1)); + diff --git a/tests/unit/resources/SimID_886118677_0_.functions b/tests/unit/resources/SimID_886118677_0_.functions new file mode 100644 index 000000000..d82a8d6eb --- /dev/null +++ b/tests/unit/resources/SimID_886118677_0_.functions @@ -0,0 +1,6 @@ +##--------------------------------------------- +## /simdata/ldresche/SimID_886118677_0_.functions +##--------------------------------------------- + +Compartment::J_r0; ((0.5 * s0) - (0.2 * s1)); ; Nonspatial_VariableType; false + diff --git a/tests/unit/resources/SimID_886118677_0_.ida.expected b/tests/unit/resources/SimID_886118677_0_.ida.expected new file mode 100644 index 000000000..c0a71f95f --- /dev/null +++ b/tests/unit/resources/SimID_886118677_0_.ida.expected @@ -0,0 +1,159 @@ +t:s0:s1: +0.00000000000000000E+00 1.00000000000000000E+00 0.00000000000000000E+00 +1.02648488190150711E-10 9.99999999948675722E-01 5.13242440913875063E-11 +1.02658753038969712E-06 9.99999486706603657E-01 5.13293396373231300E-07 +1.12914363494047687E-05 9.99994354310467903E-01 5.64568953210529354E-06 +4.48211895830175010E-05 9.99977589784958321E-01 2.24102150417055293E-05 +1.17277047540880841E-04 9.99941363945291539E-01 5.86360547084540949E-05 +2.53272368309885381E-04 9.99873375142890652E-01 1.26624857109333797E-04 +4.96024984269423201E-04 9.99752030703600969E-01 2.47969296398973030E-04 +9.22698163574049712E-04 9.99538800058830001E-01 4.61199941169868684E-04 +1.68044365853622663E-03 9.99160272360433921E-01 8.39727639565850115E-04 +3.18862269929603417E-03 9.98407466672823207E-01 1.59253332717652126E-03 +5.45114110140219790E-03 9.97279622342691741E-01 2.72037765730797233E-03 +7.71365950350836119E-03 9.96153562661734271E-01 3.84643733826544416E-03 +9.97617790561452535E-03 9.95029284922585688E-01 4.97071507741405488E-03 +1.22386963077206895E-02 9.93906786345072457E-01 6.09321365492729479E-03 +1.45012147098268537E-02 9.92786064126930712E-01 7.21393587306905053E-03 +1.73952544462393256E-02 9.91355107543508862E-01 8.64489245649092790E-03 +2.02892941826517993E-02 9.89927047398444282E-01 1.00729526015555741E-02 +2.31833339190642730E-02 9.88501877601865031E-01 1.14981223981348198E-02 +2.60773736554767467E-02 9.87079592114147530E-01 1.29204078858523308E-02 +3.29068688347819438E-02 9.83734614715639721E-01 1.62653852843602023E-02 +3.97363640140871374E-02 9.80405590318767395E-01 1.95944096812325566E-02 +5.02957748456068915E-02 9.75289650084949389E-01 2.47103499150505036E-02 +6.08551856771266456E-02 9.70211385257129355E-01 2.97886147428706068E-02 +7.14145965086463996E-02 9.65170518189519044E-01 3.48294818104809070E-02 +8.19740073401661606E-02 9.60166773545884755E-01 3.98332264541152309E-02 +9.25334181716859216E-02 9.55199878101650190E-01 4.48001218983497956E-02 +1.09712253292456846E-01 9.47197402212381134E-01 5.28025977876188587E-02 +1.26891088413227771E-01 9.39290581299578320E-01 6.07094187004216662E-02 +1.44069923533998695E-01 9.31478272015571096E-01 6.85217279844288346E-02 +1.61248758654769619E-01 9.23759344630696533E-01 7.62406553693034117E-02 +1.89660049003499281E-01 9.11195316277009670E-01 8.88046837229902880E-02 +2.18071339352228943E-01 8.98878691567765453E-01 1.01121308432234533E-01 +2.46482629700958605E-01 8.86804598883420114E-01 1.13195401116579816E-01 +2.74893920049688267E-01 8.74968262536995778E-01 1.25031737463004083E-01 +3.03305210398417957E-01 8.63365000789078185E-01 1.36634999210921676E-01 +3.31716500747147647E-01 8.51990224040054867E-01 1.48009775959944967E-01 +3.60127791095877337E-01 8.40839433068397923E-01 1.59160566931601855E-01 +3.88539081444607026E-01 8.29908217266169590E-01 1.70091782733830132E-01 +4.41138445070370255E-01 8.10235627189399343E-01 1.89764372810600435E-01 +4.77567574856914034E-01 7.97029226508457089E-01 2.02970773491542689E-01 +5.13996704643457814E-01 7.84155337872014080E-01 2.15844662127985643E-01 +5.50425834430001593E-01 7.71605588560225097E-01 2.28394411439774653E-01 +5.86854964216545372E-01 7.59371816557104728E-01 2.40628183442894966E-01 +6.23284094003089151E-01 7.47446066126369102E-01 2.52553933873630565E-01 +6.59713223789632930E-01 7.35820582268969181E-01 2.64179417731030486E-01 +6.96142353576176709E-01 7.24487805053363076E-01 2.75512194946636535E-01 +7.58815310233661888E-01 7.05654481953680968E-01 2.94345518046318699E-01 +8.21488266891147068E-01 6.87629536154125143E-01 3.12370463845874413E-01 +8.84161223548632247E-01 6.70378268923642939E-01 3.29621731076356561E-01 +9.46834180206117426E-01 6.53867470964243247E-01 3.46132529035756198E-01 +1.00950713686360261E+00 6.38065359483856231E-01 3.61934640516143269E-01 +1.07218009352108767E+00 6.22941516449129762E-01 3.77058483550869794E-01 +1.13485305017857274E+00 6.08466829122856878E-01 3.91533170877142678E-01 +1.19752600683605781E+00 5.94613433892461130E-01 4.05386566107538426E-01 +1.26019896349354288E+00 5.81354663050361431E-01 4.18645336949638014E-01 +1.32287192015102795E+00 5.68664993725449097E-01 4.31335006274550459E-01 +1.38554487680851302E+00 5.56519998669646965E-01 4.43480001330352702E-01 +1.44821783346599808E+00 5.44896299070346424E-01 4.55103700929653243E-01 +1.51089079012348315E+00 5.33771519516928472E-01 4.66228480483071195E-01 +1.57356374678096822E+00 5.23124244999401800E-01 4.76875755000597867E-01 +1.63623670343845329E+00 5.12933979731394318E-01 4.87066020268605349E-01 +1.69890966009593836E+00 5.03181107681094364E-01 4.96818892318905247E-01 +1.76158261675342342E+00 4.93846854780748468E-01 5.06153145219251144E-01 +1.82425557341090849E+00 4.84913252781268178E-01 5.15086747218731489E-01 +1.88692853006839356E+00 4.76363104676305715E-01 5.23636895323693952E-01 +1.94960148672587863E+00 4.68179951606793432E-01 5.31820048393206291E-01 +2.01227444338336392E+00 4.60348041167775057E-01 5.39651958832224610E-01 +2.07494740004084921E+00 4.52852297094320178E-01 5.47147702905679489E-01 +2.13762035669833450E+00 4.45678290239669095E-01 5.54321709760330572E-01 +2.20029331335581979E+00 4.38812210792762725E-01 5.61187789207236998E-01 +2.26296627001330508E+00 4.32240841692172695E-01 5.67759158307827083E-01 +2.32563922667079037E+00 4.25951533186333664E-01 5.74048466813666169E-01 +2.38831218332827566E+00 4.19932178485779350E-01 5.80067821514220539E-01 +2.45098513998576095E+00 4.14171190457713889E-01 5.85828809542286000E-01 +2.51365809664324624E+00 4.08657479319653238E-01 5.91342520680346651E-01 +2.57633105330073153E+00 4.03380431291335495E-01 5.96619568708664394E-01 +2.63900400995821682E+00 3.98329888163913515E-01 6.01670111836086319E-01 +2.70167696661570211E+00 3.93496127746071123E-01 6.06503872253928655E-01 +2.76434992327318740E+00 3.88869845148941795E-01 6.11130154851058038E-01 +2.82702287993067269E+00 3.84442134874129537E-01 6.15557865125870296E-01 +2.88969583658815798E+00 3.80204473670731957E-01 6.19795526329267821E-01 +2.95236879324564327E+00 3.76148704128358324E-01 6.23851295871641454E-01 +3.01504174990312857E+00 3.72267018974363595E-01 6.27732981025636239E-01 +3.07771470656061386E+00 3.68551946044992529E-01 6.31448053955007249E-01 +3.14038766321809915E+00 3.64996333901570436E-01 6.35003666098429287E-01 +3.20306061987558444E+00 3.61593338064120084E-01 6.38406661935879582E-01 +3.26573357653306973E+00 3.58336407835898707E-01 6.41663592164100960E-01 +3.32840653319055502E+00 3.55219273693456239E-01 6.44780726306543484E-01 +3.39107948984804031E+00 3.52235935217927731E-01 6.47764064782072047E-01 +3.45375244650552560E+00 3.49380649544342170E-01 6.50619350455657663E-01 +3.51642540316301089E+00 3.46647920306724255E-01 6.53352079693275467E-01 +3.57909835982049618E+00 3.44032487057707448E-01 6.55967512942292386E-01 +3.64177131647798147E+00 3.41529315142283463E-01 6.58470684857716426E-01 +3.70444427313546676E+00 3.39133586006194754E-01 6.60866413993805191E-01 +3.76711722979295205E+00 3.36840687920315074E-01 6.63159312079684926E-01 +3.82979018645043734E+00 3.34646207103166171E-01 6.65353792896833829E-01 +3.89246314310792263E+00 3.32545919224480291E-01 6.67454080775519709E-01 +3.95513609976540792E+00 3.30535781273451845E-01 6.69464218726548155E-01 +4.01780905642289277E+00 3.28611923776025050E-01 6.71388076223975006E-01 +4.08048201308037761E+00 3.26770643346235901E-01 6.73229356653764155E-01 +4.14315496973786246E+00 3.25008395557270624E-01 6.74991604442729431E-01 +4.20582792639534730E+00 3.23321788118517361E-01 6.76678211881482694E-01 +4.26850088305283215E+00 3.21707574345476921E-01 6.78292425654523079E-01 +4.36259663226557493E+00 3.19413195181034726E-01 6.80586804818965274E-01 +4.45669238147831770E+00 3.17265070740067878E-01 6.82734929259932066E-01 +4.55078813069106047E+00 3.15253877871656929E-01 6.84746122128343071E-01 +4.64488387990380325E+00 3.13370887632055728E-01 6.86629112367944217E-01 +4.73897962911654602E+00 3.11607927741266999E-01 6.88392072258733001E-01 +4.83307537832928880E+00 3.09957347043537612E-01 6.90042652956462388E-01 +4.92717112754203157E+00 3.08411982061210621E-01 6.91588017938789434E-01 +5.02126687675477434E+00 3.06965125834067321E-01 6.93034874165932679E-01 +5.11536262596751712E+00 3.05610498904494543E-01 6.94389501095505457E-01 +5.20945837518025989E+00 3.04342222141284424E-01 6.95657777858715631E-01 +5.30355412439300267E+00 3.03154791209282182E-01 6.96845208790717763E-01 +5.39764987360574544E+00 3.02043052635592169E-01 6.97956947364407831E-01 +5.49174562281848821E+00 3.01002181432545757E-01 6.98997818567454243E-01 +5.58584137203123099E+00 3.00027660175653355E-01 6.99972339824346701E-01 +5.67993712124397376E+00 2.99115259411111445E-01 7.00884740588888611E-01 +5.77403287045671654E+00 2.98261019295870233E-01 7.01738980704129878E-01 +5.86812861966945931E+00 2.97461232403345710E-01 7.02538767596654345E-01 +5.96222436888220209E+00 2.96712427631590503E-01 7.03287572368409553E-01 +6.05632011809494486E+00 2.96011355142305865E-01 7.03988644857694190E-01 +6.15041586730768763E+00 2.95354972258738302E-01 7.04645027741261698E-01 +6.24451161652043041E+00 2.94740430259425534E-01 7.05259569740574466E-01 +6.33860736573317318E+00 2.94165062013088063E-01 7.05834937986911881E-01 +6.43270311494591596E+00 2.93626370403025250E-01 7.06373629596974695E-01 +6.52679886415865873E+00 2.93122017490281217E-01 7.06877982509718672E-01 +6.62089461337140150E+00 2.92649814367307026E-01 7.07350185632692807E-01 +6.71499036258414428E+00 2.92207711657821112E-01 7.07792288342178666E-01 +6.80908611179688705E+00 2.91793790622163152E-01 7.08206209377836515E-01 +6.90318186100962983E+00 2.91406254829892186E-01 7.08593745170107536E-01 +6.99727761022237260E+00 2.91043422363364113E-01 7.08956577636635665E-01 +7.09137335943511538E+00 2.90703718518221410E-01 7.09296281481778479E-01 +7.18546910864785815E+00 2.90385668969080357E-01 7.09614331030919643E-01 +7.27956485786060092E+00 2.90087893370859995E-01 7.09912106629139950E-01 +7.37366060707334370E+00 2.89809099368042500E-01 7.10190900631957334E-01 +7.46775635628608647E+00 2.89548076985837011E-01 7.10451923014162823E-01 +7.56185210549882925E+00 2.89303693378861304E-01 7.10696306621138585E-01 +7.65594785471157202E+00 2.89074887914547385E-01 7.10925112085452504E-01 +7.75004360392431479E+00 2.88860667569953156E-01 7.11139332430046678E-01 +7.84413935313705757E+00 2.88660102622012638E-01 7.11339897377987196E-01 +7.93823510234980034E+00 2.88472322612513654E-01 7.11527677387486235E-01 +8.08080669531667084E+00 2.88210359800015581E-01 7.11789640199984253E-01 +8.22337828828354134E+00 2.87973278978804759E-01 7.12026721021195130E-01 +8.36594988125041183E+00 2.87758716623703892E-01 7.12241283376295997E-01 +8.50852147421728233E+00 2.87564533611528172E-01 7.12435466388471661E-01 +8.65109306718415283E+00 2.87388794232750655E-01 7.12611205767249123E-01 +8.79366466015102333E+00 2.87229746815752529E-01 7.12770253184247249E-01 +8.93623625311789382E+00 2.87085806022613133E-01 7.12914193977386645E-01 +9.07880784608476432E+00 2.86955536978731940E-01 7.13044463021267783E-01 +9.22137943905163482E+00 2.86837641067565152E-01 7.13162358932434515E-01 +9.36395103201850532E+00 2.86730943062506516E-01 7.13269056937493207E-01 +9.50652262498537581E+00 2.86634379394257510E-01 7.13365620605742157E-01 +9.64909421795224631E+00 2.86546987502534933E-01 7.13453012497464734E-01 +9.79166581091911681E+00 2.86467896231261898E-01 7.13532103768737769E-01 +9.93423740388598731E+00 2.86396317164659742E-01 7.13603682835339925E-01 +1.00000000000000000E+01 2.86365632344379117E-01 7.13634367655620494E-01 diff --git a/tests/unit/resources/SimID_886118677_0_.log b/tests/unit/resources/SimID_886118677_0_.log new file mode 100644 index 000000000..11b4c8711 --- /dev/null +++ b/tests/unit/resources/SimID_886118677_0_.log @@ -0,0 +1,4 @@ +IDAData logfile +IDAData text format version 1 +SimID_886118677_0_.ida +KeepMost 1000 diff --git a/tests/unit/smoke_test.cpp b/tests/unit/smoke_test.cpp index 18d94e1ee..b003a75b9 100644 --- a/tests/unit/smoke_test.cpp +++ b/tests/unit/smoke_test.cpp @@ -35,12 +35,7 @@ void performSmokeTest(const int taskID, const int hashID) { throw std::runtime_error("Could not open output file[" + OUTPUT_TARGET.string() + "] for writing."); } - try { - activateSolver(inputFileStream, outputFile, taskID); - } catch (const std::exception& e) { - std::cerr << "Error caught in test: " << e.what() << std::endl; - exit(EXIT_FAILURE); - } + activateSolver(inputFileStream, outputFile, taskID); fclose(outputFile); compare(OUTPUT_TARGET, NECESSARY_FILES[1], 1e-7); From 91ee797dc733ec8fee04876a8421a5d930e9644e Mon Sep 17 00:00:00 2001 From: Logan Drescher Date: Wed, 10 Dec 2025 13:33:07 -0500 Subject: [PATCH 22/34] Added logging library --- CMakeLists.txt | 14 ++++++++++++++ IDAWin/CMakeLists.txt | 2 +- VCellMessaging/CMakeLists.txt | 2 +- conanfile.py | 1 + tests/CMakeLists.txt | 2 ++ 5 files changed, 19 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 1be1bd6bf..6f8c6faa4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -28,6 +28,10 @@ endif () ############################################## option(OPTION_EXTRA_CONFIG_INFO "Print useful cmake debug info while configuring" OFF) +if (MINGW) + message("ERROR: Due to the requirements of the project, mingw builds are currently disabled. Native windows builds should still work.") + return() +endif () ############################################# # # Build 64bit binaries on Mac and target Macos 10.7 or later @@ -144,6 +148,16 @@ endif(WIN32) ############################################# # installation directories ############################################# +# You can install spdlog with the following: +# > Debian: sudo apt install libspdlog-dev +# > Homebrew: brew install spdlog +# > Conan: conan install --requires=spdlog/[*] ( it may be easier just to add to the conan file!) +# > vcpkg: vcpkg install spdlog +if(NOT TARGET spdlog) + # Stand-alone build + find_package(spdlog REQUIRED) +endif() + if (NOT OPTION_TARGET_MESSAGING) set(OPTION_EXE_DIRECTORY "bin" CACHE PATH "installation directory") else (NOT OPTION_TARGET_MESSAGING) diff --git a/IDAWin/CMakeLists.txt b/IDAWin/CMakeLists.txt index b6d66cc54..32a3a3801 100644 --- a/IDAWin/CMakeLists.txt +++ b/IDAWin/CMakeLists.txt @@ -29,7 +29,7 @@ set (EXE_SRC_FILES ) add_library(IDAWin STATIC ${SRC_FILES} ${HEADER_FILES}) -target_link_libraries(IDAWin sundials ExpressionParser vcellmessaging argparse) +target_link_libraries(IDAWin sundials ExpressionParser vcellmessaging argparse spdlog::spdlog) # if MinGW is enabled, add `$<$:ws2_32>` set(EXE_FILE SundialsSolverStandalone) if (ARCH_64bit) set(EXE_FILE ${EXE_FILE}_x64) diff --git a/VCellMessaging/CMakeLists.txt b/VCellMessaging/CMakeLists.txt index 633117474..3e59227b9 100644 --- a/VCellMessaging/CMakeLists.txt +++ b/VCellMessaging/CMakeLists.txt @@ -33,7 +33,7 @@ if (OPTION_TARGET_MESSAGING) message(STATUS "CURL_INCLUDE_DIR = '${CURL_INCLUDE_DIR}'") endif () - target_link_libraries(vcellmessaging ${CURL_LIBRARIES} Threads::Threads) + target_link_libraries(vcellmessaging ${CURL_LIBRARIES} Threads::Threads spdlog::spdlog) # if MinGW is enabled, add `$<$:ws2_32>` target_compile_definitions(vcellmessaging PUBLIC USE_MESSAGING=1) target_include_directories(vcellmessaging PUBLIC ${CURL_INCLUDE_DIR}) endif() diff --git a/conanfile.py b/conanfile.py index 2e7c89975..c069ba23d 100644 --- a/conanfile.py +++ b/conanfile.py @@ -31,6 +31,7 @@ def build(self): def requirements(self): self.requires("argparse/[>=3.2 <4.0]") + self.requires("spdlog/[>=1.16.0 <2.0]") if self.options.include_messaging: self.requires("libcurl/[<9.0]") diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 824aacb0b..731325ee4 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -24,6 +24,8 @@ add_executable(unit_tests target_link_libraries(unit_tests PRIVATE IDAWin GTest::gtest_main + spdlog::spdlog + # if MinGW is enabled, add `$<$:ws2_32>` ) gtest_discover_tests(unit_tests) From 9581e7a334cee60a9cfb621550aed24cb95ec6ac Mon Sep 17 00:00:00 2001 From: Logan Drescher Date: Wed, 10 Dec 2025 14:12:59 -0500 Subject: [PATCH 23/34] fixed upload paths --- .github/workflows/cd.yml | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index 3c237162d..61b3db769 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -216,46 +216,45 @@ jobs: uses: actions/upload-artifact@v4 with: name: macos_x86_64.tgz - path: build/upload/mac_x86_64.tgz + path: build/upload/mac64.tgz - name: Upload ARM Macos binaries if: matrix.platform == 'macos-latest' uses: actions/upload-artifact@v4 with: name: macos_arm64.tgz - path: build/upload/mac_arm_64.tgz + path: build/upload/mac64.tgz - name: Upload Windows (x86_64) binaries if: matrix.platform == 'windows-latest' uses: actions/upload-artifact@v4 with: - name: win64.zip - path: build/upload/win_x86_64.zip + name: win64_x86_64.zip + path: build/upload/win64.zip - name: Upload Windows (ARMv8) binaries if: matrix.platform == 'windows-11-arm' uses: actions/upload-artifact@v4 with: - name: win64.zip - path: build/upload/win_ARMv8_64.zip + name: win64_arm64.zip + path: build/upload/win64.zip - name: Upload Linux (x86_64) binaries if: matrix.platform == 'ubuntu-latest' uses: actions/upload-artifact@v4 with: - name: linux64.tgz - path: build/upload/linux_x86_64.tgz + name: linux64_x86_64.tgz + path: build/upload/linux64.tgz - name: Upload Linux (ARMv8) binaries if: matrix.platform == 'ubuntu-24.04-arm' uses: actions/upload-artifact@v4 with: - name: linux64.tgz - path: build/upload/linux_ARMv8_64.tgz + name: linux64_arm64.tgz + path: build/upload/linux64.tgz - name: Setup tmate session if: ${{ failure() }} uses: mxschmitt/action-tmate@v3 with: limit-access-to-actor: false - From fd95e2413457661d967a47469415baa293eefc3e Mon Sep 17 00:00:00 2001 From: Logan Drescher Date: Thu, 11 Dec 2025 15:55:17 -0500 Subject: [PATCH 24/34] updating universal libraries code --- .github/scripts/install_name_tool_macos.sh | 5 +- .github/workflows/cd.yml | 373 ++++++++++++++------- CMakeLists.txt | 32 +- IDAWin/CMakeLists.txt | 23 +- 4 files changed, 298 insertions(+), 135 deletions(-) diff --git a/.github/scripts/install_name_tool_macos.sh b/.github/scripts/install_name_tool_macos.sh index 9cd9d1d03..4f54f49ec 100755 --- a/.github/scripts/install_name_tool_macos.sh +++ b/.github/scripts/install_name_tool_macos.sh @@ -8,11 +8,12 @@ shopt -s -o nounset for exe in `ls *_x64` do echo "fixing paths in ${exe}" - for libpath in `otool -L ${exe} | grep "/" | grep -v "System" | awk '{print $1}'` + for libpath in `otool -L ${exe} | grep "/" | grep -v "/usr/lib" | grep -v "System" | awk '{print $1}'` do libfilename=${libpath##*/} echo install_name_tool -change $libpath @executable_path/$libfilename $exe install_name_tool -change $libpath @executable_path/$libfilename $exe + cp libpath "$(pwd)" done done @@ -29,7 +30,7 @@ do echo install_name_tool -id "@loader_path/$libfilename" $libfilename install_name_tool -id "@loader_path/$libfilename" $libfilename - for dependentlibpath in `otool -L ${libfilename} | grep "/" | grep -v "System" | awk '{print $1}'` + for dependentlibpath in `otool -L ${libfilename} | grep "/" | grep -v "/usr/lib" | grep -v "System" | awk '{print $1}'` do dependentlibfilename=${dependentlibpath##*/} # diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index 61b3db769..f44c660e0 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -3,29 +3,115 @@ name: CD on: [push, workflow_dispatch] jobs: - native-build: - name: - native-build-${{ matrix.platform }} + MacOS: + name: native-build-${{ matrix.platform }} + runs-on: ${{ matrix.platform }} strategy: fail-fast: false matrix: - platform: [windows-latest, ubuntu-latest, ubuntu-24.04-arm, macos-15-intel, macos-latest] - #platform: [windows-latest, windows-11-arm, ubuntu-latest, ubuntu-24.04-arm, macos-15-intel, macos-latest] + platform: [ macos-15-intel, macos-latest ] - runs-on: ${{ matrix.platform }} + steps: + - name: checkout vcell-ode repo + uses: actions/checkout@v4 + + - name: Install MacOS dependencies + if: matrix.platform == 'macos-15-intel' + shell: bash + run: | + brew install conan + conan --version + mkdir -p ~/.conan2/profiles/ + touch ~/.conan2/profiles/default # if we don't make a file first, cp thinks its a folder that doesn't exist + cp conan-profiles/CI-CD/MacOS-AMD64_profile.txt ~/.conan2/profiles/default + + - name: Install MacOS dependencies + if: matrix.platform == 'macos-latest' + shell: bash + run: | + brew install conan + conan --version + mkdir -p ~/.conan2/profiles/ + touch ~/.conan2/profiles/default # if we don't make a file first, cp thinks its a folder that doesn't exist + cp conan-profiles/CI-CD/MacOS-ARM64_profile.txt ~/.conan2/profiles/default + + - name: Install Dependencies through Conan on MacOS + if: matrix.platform == 'macos-latest' || matrix.platform == 'macos-15-intel' + shell: bash + run: | + conan install . --output-folder build --build=missing + + - name: Build Mac + run: | + echo "working dir is $PWD" + cd build + source conanbuild.sh + cmake -B . -S .. -G "Ninja" -DCMAKE_TOOLCHAIN_FILE="conan_toolchain.cmake" -DCMAKE_BUILD_TYPE=Release + cmake --build . --config Release + + - name: Test Mac + run: | + cd build + + ctest -VV + + echo "------ running SundialsSolverStandalone_x64 ------" + ./bin/SundialsSolverStandalone_x64 || true + + + - name: Make MacOS Shared Object Paths Relative + shell: bash + run: | + mkdir build/upload + cd build/bin + rm unit_tests + ls *_x64 | awk '{print $1}' | xargs -I '{}' otool -L '{}' | grep ")" | grep -v "build" | grep -v "System" | awk '{print $1}' | xargs -I '{}' cp -vn '{}' . || true + ls *.dylib | awk '{print $1}' | xargs -I '{}' otool -L '{}' | grep ")" | grep -v "build" | grep -v "System" | awk '{print $1}' | xargs -I '{}' cp -vn '{}' . || true + ls *.dylib | awk '{print $1}' | xargs -I '{}' otool -L '{}' | grep ")" | grep -v "build" | grep -v "System" | awk '{print $1}' | xargs -I '{}' cp -vn '{}' . || true + chmod u+w,+x * + tar czvf ../upload/mac64_bad_paths.tgz . + ../../.github/scripts/install_name_tool_macos.sh + tar czvf ../upload/mac64.tgz --dereference . + + - name: Upload Intel Macos binaries + if: matrix.platform == 'macos-15-intel' + uses: actions/upload-artifact@v4 + with: + name: macos_x86_64 + path: build/upload/mac64.tgz + + - name: Upload ARM Macos binaries + if: matrix.platform == 'macos-latest' + uses: actions/upload-artifact@v4 + with: + name: macos_arm64 + path: build/upload/mac64.tgz + - name: Setup tmate session + if: ${{ failure() }} + uses: mxschmitt/action-tmate@v3 + with: + limit-access-to-actor: false + + + WindowsOS: + name: native-build-${{ matrix.platform }} + runs-on: ${{ matrix.platform }} + strategy: + fail-fast: false + matrix: + #platform: [ windows-latest, windows-11-arm ] + platform: [ windows-latest ] steps: - name: checkout vcell-ode repo uses: actions/checkout@v4 - name: Install Windows Dependencies (Part 0 - Setup LLVM-style) - if: matrix.platform == 'windows-latest' || matrix.platform == 'windows-11-arm' uses: llvm/actions/setup-windows@main with: arch: amd64 - name: Install Windows Dependencies (Part 1 - Configure Conan ...and zip) - if: matrix.platform == 'windows-latest' || matrix.platform == 'windows-11-arm' shell: powershell run: | choco install zip -y @@ -50,25 +136,71 @@ jobs: run: | cp conan-profiles/CI-CD/Windows-ARM64_profile.txt $CONAN_HOME/profiles/default - - name: Install MacOS dependencies - if: matrix.platform == 'macos-15-intel' - shell: bash + - name: Install Dependencies through Conan on Windows + shell: powershell run: | - brew install conan - conan --version - mkdir -p ~/.conan2/profiles/ - touch ~/.conan2/profiles/default # if we don't make a file first, cp thinks its a folder that doesn't exist - cp conan-profiles/CI-CD/MacOS-AMD64_profile.txt ~/.conan2/profiles/default + conan install . --output-folder build --build=missing -o include_messaging=False - - name: Install MacOS dependencies - if: matrix.platform == 'macos-latest' + - name: Build Windows + shell: powershell + run: | + cd build + ./conanbuild.ps1 + cmake -B . -S .. -G "Ninja" -DCMAKE_TOOLCHAIN_FILE="conan_toolchain.cmake" -DCMAKE_BUILD_TYPE=Release + cmake --build . --config Release + + - name: Test Windows + run: | + cd build + ctest -VV + ./bin/SundialsSolverStandalone_x64 || true + + - name: Make Windows Shared Object Paths Relative shell: bash run: | - brew install conan - conan --version - mkdir -p ~/.conan2/profiles/ - touch ~/.conan2/profiles/default # if we don't make a file first, cp thinks its a folder that doesn't exist - cp conan-profiles/CI-CD/MacOS-ARM64_profile.txt ~/.conan2/profiles/default + mkdir build/upload + cd build/bin + rm unit_tests* *.pdb || true + ls *.exe | awk '{print $1}' | xargs -I '{}' ldd '{}' | grep '=> /' | grep -v build | grep -iv windows | awk '{print $3}' | xargs -I '{}' cp -vn '{}' . || true + # Currently, Sundials only requires system32 dlls! + # ls *.dll | awk '{print $1}' | xargs -I '{}' ldd '{}' | grep '=> /' | grep -v build | grep -iv windows | awk '{print $3}' | xargs -I '{}' cp -vn '{}' . || true + # ls *.dll | awk '{print $1}' | xargs -I '{}' ldd '{}' | grep '=> /' | grep -v build | grep -iv windows | awk '{print $3}' | xargs -I '{}' cp -vn '{}' . || true + chmod u+w,+x * + zip ../upload/win64.zip ./* + cd ../.. + # fi + + - name: Upload Windows (x86_64) binaries + if: matrix.platform == 'windows-latest' + uses: actions/upload-artifact@v4 + with: + name: win64_x86_64 + path: build/upload/win64.zip + + - name: Upload Windows (ARMv8) binaries + if: matrix.platform == 'windows-11-arm' + uses: actions/upload-artifact@v4 + with: + name: win64_arm64 + path: build/upload/win64.zip + + - name: Setup tmate session + if: ${{ failure() }} + uses: mxschmitt/action-tmate@v3 + with: + limit-access-to-actor: false + + GNULinux: + name: native-build-${{ matrix.platform }} + runs-on: ${{ matrix.platform }} + strategy: + fail-fast: false + matrix: + platform: [ ubuntu-latest, ubuntu-24.04-arm ] + + steps: + - name: checkout vcell-ode repo + uses: actions/checkout@v4 - name: Install Linux Dependencies if: matrix.platform == 'ubuntu-latest' @@ -94,17 +226,6 @@ jobs: touch ~/.conan2/profiles/default # if we don't make a file first, cp thinks its a folder that doesn't exist cp conan-profiles/CI-CD/Linux-ARM64_profile.txt ~/.conan2/profiles/default - - name: Install Dependencies through Conan on Windows - if: matrix.platform == 'windows-latest' || matrix.platform == 'windows-11-arm' - shell: powershell - run: | - conan install . --output-folder build --build=missing -o include_messaging=False - - - name: Install Dependencies through Conan on MacOS - if: matrix.platform == 'macos-latest' || matrix.platform == 'macos-15-intel' - shell: bash - run: | - conan install . --output-folder build --build=missing - name: Install Dependencies through Conan on Linux if: matrix.platform == 'ubuntu-latest' @@ -130,17 +251,7 @@ jobs: sudo ln -s $(which mold) /usr/bin/ld conan install . --output-folder build --build=missing - - name: Build Windows - if: matrix.platform == 'windows-latest' || matrix.platform == 'windows-11-arm' - shell: powershell - run: | - cd build - ./conanbuild.ps1 - cmake -B . -S .. -G "Ninja" -DCMAKE_TOOLCHAIN_FILE="conan_toolchain.cmake" -DCMAKE_BUILD_TYPE=Release - cmake --build . --config Release - - - name: Build Non-Intel-Mac Unix - if: matrix.platform == 'ubuntu-latest' || matrix.platform == 'ubuntu-24.04-arm' || matrix.platform == 'macos-15-intel' || matrix.platform == 'macos-latest' + - name: Build Linux run: | echo "working dir is $PWD" cd build @@ -148,62 +259,22 @@ jobs: cmake -B . -S .. -G "Ninja" -DCMAKE_TOOLCHAIN_FILE="conan_toolchain.cmake" -DCMAKE_BUILD_TYPE=Release cmake --build . --config Release - - name: Test Windows - if: matrix.platform == 'windows-latest' || matrix.platform == 'windows-11-arm' - run: | - cd build - ctest -VV - ./bin/SundialsSolverStandalone_x64 || true - - - name: Test Unix - if: matrix.platform == 'ubuntu-latest' || matrix.platform == 'ubuntu-24.04-arm' || matrix.platform == 'macos-15-intel' || matrix.platform == 'macos-latest' + - name: Test Linux run: | cd build ctest -VV - + echo "------ running SundialsSolverStandalone_x64 ------" ./bin/SundialsSolverStandalone_x64 || true - - name: fix Macos shared object paths - if: matrix.platform == 'macos-15-intel' || matrix.platform == 'macos-latest' - shell: bash - run: | - mkdir build/upload - cd build/bin - rm hello_test TestVCellStoch testzip ziptool || true - ls *_x64 | awk '{print $1}' | xargs -I '{}' otool -L '{}' | grep ")" | grep -v "build" | grep -v "System" | awk '{print $1}' | xargs -I '{}' cp -vn '{}' . || true - ls *.dylib | awk '{print $1}' | xargs -I '{}' otool -L '{}' | grep ")" | grep -v "build" | grep -v "System" | awk '{print $1}' | xargs -I '{}' cp -vn '{}' . || true - ls *.dylib | awk '{print $1}' | xargs -I '{}' otool -L '{}' | grep ")" | grep -v "build" | grep -v "System" | awk '{print $1}' | xargs -I '{}' cp -vn '{}' . || true - chmod u+w,+x * - tar czvf ../upload/mac64_bad_paths.tgz . - ../../.github/scripts/install_name_tool_macos.sh - tar czvf ../upload/mac64.tgz --dereference . - - - name: handle shared object paths for Windows native build - if: matrix.platform == 'windows-latest' || matrix.platform == 'windows-11-arm' - shell: bash - run: | - mkdir build/upload - cd build/bin - rm hello_test* *.pdb || true - ls *.exe | awk '{print $1}' | xargs -I '{}' ldd '{}' | grep '=> /' | grep -v build | grep -iv windows | awk '{print $3}' | xargs -I '{}' cp -vn '{}' . || true - # Currently, Sundials only requires system32 dlls! - # ls *.dll | awk '{print $1}' | xargs -I '{}' ldd '{}' | grep '=> /' | grep -v build | grep -iv windows | awk '{print $3}' | xargs -I '{}' cp -vn '{}' . || true - # ls *.dll | awk '{print $1}' | xargs -I '{}' ldd '{}' | grep '=> /' | grep -v build | grep -iv windows | awk '{print $3}' | xargs -I '{}' cp -vn '{}' . || true - chmod u+w,+x * - zip ../upload/win64.zip ./* - cd ../.. - # fi - - - name: handle shared object paths for Linux native build - if: matrix.platform == 'ubuntu-latest' || matrix.platform == 'ubuntu-24.04-arm' + - name: Make Linux Shared Object Paths Relative shell: bash run: | mkdir build/upload cd build/bin - rm hello_test TestVCellStoch testzip ziptool || true + rm unit_tests ls *_x64 | awk '{print $1}' | xargs -I '{}' ldd '{}' | grep "=> /" | grep -v "build" | awk '{print $3}' | xargs -I '{}' cp -vn '{}' . || true ls *.so | awk '{print $1}' | xargs -I '{}' ldd '{}' | grep "=> /" | grep -v "build" | awk '{print $3}' | xargs -I '{}' cp -vn '{}' . || true ls *.so | awk '{print $1}' | xargs -I '{}' ldd '{}' | grep "=> /" | grep -v "build" | awk '{print $3}' | xargs -I '{}' cp -vn '{}' . || true @@ -211,50 +282,118 @@ jobs: tar czvf ../upload/linux64.tgz --dereference . cd ../.. - - name: Upload Intel Macos binaries - if: matrix.platform == 'macos-15-intel' + - name: Upload Linux (x86_64) binaries + if: matrix.platform == 'ubuntu-latest' uses: actions/upload-artifact@v4 with: - name: macos_x86_64.tgz - path: build/upload/mac64.tgz + name: linux64_x86_64 + path: build/upload/linux64.tgz - - name: Upload ARM Macos binaries - if: matrix.platform == 'macos-latest' + - name: Upload Linux (ARMv8) binaries + if: matrix.platform == 'ubuntu-24.04-arm' uses: actions/upload-artifact@v4 with: - name: macos_arm64.tgz - path: build/upload/mac64.tgz + name: linux64_arm64 + path: build/upload/linux64.tgz - - name: Upload Windows (x86_64) binaries - if: matrix.platform == 'windows-latest' - uses: actions/upload-artifact@v4 + - name: Setup tmate session + if: ${{ failure() }} + uses: mxschmitt/action-tmate@v3 with: - name: win64_x86_64.zip - path: build/upload/win64.zip + limit-access-to-actor: false - - name: Upload Windows (ARMv8) binaries - if: matrix.platform == 'windows-11-arm' - uses: actions/upload-artifact@v4 + + MacOS-Universal: + name: Create Universal macOS Binary + runs-on: macos-latest + needs: + - MacOS + + + steps: + # The binaries are "named" (ID'd) different as artifacts, but not as filenames (`mac64.tgz`). + # As soon as we download one of them we need to rename it to avoid overwriting the other one. + - name: Download macOS-Arm64 binary + uses: actions/download-artifact@v4 with: - name: win64_arm64.zip - path: build/upload/win64.zip + name: macos_arm64 + path: binaries - - name: Upload Linux (x86_64) binaries - if: matrix.platform == 'ubuntu-latest' - uses: actions/upload-artifact@v4 + - name: Unpack & Rename macOS-Arm64 binary + run: | + mkdir binaries/arm + tar xzvf binaries/mac64.tgz -C binaries/arm + rm binaries/mac64.tgz + + - name: Download macOS-x86_64 binary + uses: actions/download-artifact@v4 with: - name: linux64_x86_64.tgz - path: build/upload/linux64.tgz + name: macos_x86_64 + path: binaries - - name: Upload Linux (ARMv8) binaries - if: matrix.platform == 'ubuntu-24.04-arm' + - name: Unpack & Rename macOS-x86_64 binary + run: | + mkdir binaries/intel + tar xzvf binaries/mac64.tgz -C binaries/intel + rm binaries/mac64.tgz + + # this will go in the log of the run, useful for debugging + - name: Inspect binaries directory + run: | + echo "Contents of binaries/:" + ls -Rlh binaries/ + + # - name: Setup tmate session for inspection + # uses: mxschmitt/action-tmate@v3 + + - name: Combine with lipo + run: | + mkdir binaries/universal + for exe in `ls binaries/arm/ | grep ".*x64"` + do + echo "fixing ${exe}..." + lipo -create binaries/arm/$exe binaries/intel/${exe} \ + -output binaries/universal/${exe} + done + + for dylib in `ls binaries/arm/ | grep ".*\.dylib"` + do + echo "fixing ${dylib}..." + lipo -create binaries/arm/$dylib binaries/intel/${dylib} \ + -output binaries/universal/${dylib} + done + for entry in `ls binaries/universal/` + do + echo "confirming ${entry}..." + lipo -archs binaries/universal/${entry} + done + mv binaries/universal binaries/mac64 + tar czvf binaries/macos-universal.tgz binaries/mac64 + + - name: Inspect binaries directory + run: | + echo "Contents of binaries/:" + ls -Rlh binaries/ + + - name: Upload MacOS (Universal) binaries uses: actions/upload-artifact@v4 with: - name: linux64_arm64.tgz - path: build/upload/linux64.tgz + name: macos64_universal + path: binaries/macos-universal.tgz + +# - name: Upload universal binary to release +# if: github.event_name == 'release' +# uses: actions/upload-release-asset@v1 +# env: +# GITHUB_TOKEN: ${{ github.token }} +# with: +# upload_url: ${{ github.event.release.upload_url }} +# asset_path: binaries/macos-universal.tgz +# asset_name: macos-universal +# asset_content_type: application/octet-stream - name: Setup tmate session if: ${{ failure() }} uses: mxschmitt/action-tmate@v3 with: - limit-access-to-actor: false + limit-access-to-actor: false \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 6f8c6faa4..9a001958b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -24,14 +24,30 @@ if (WIN32) endif () ############################################# -# Display extra CMAKE info while configuring +# Declare Options ############################################## +option(OPTION_TARGET_MESSAGING "Messaging (requires libcurl)" OFF) +option(OPTION_TARGET_DOCS "Generate Doxygen documentation" OFF) +option(OPTION_TEST_WITH_LOCALHOST "Use localhost as server rather than vcell.cam.uchc.edu" OFF) option(OPTION_EXTRA_CONFIG_INFO "Print useful cmake debug info while configuring" OFF) +option(OPTION_STATICALLY_LINK "Decided whether to statically link, or use (and generate) dynamic shared libs" OFF) + +############################################# +# MinGW check +############################################## if (MINGW) message("ERROR: Due to the requirements of the project, mingw builds are currently disabled. Native windows builds should still work.") return() endif () + +if (OPTION_STATICALLY_LINK) + #set(BUILD_SHARED_LIBS OFF) + set(CMAKE_EXE_LINKER_FLAGS "-static") +else () + #set(BUILD_SHARED_LIBS ON) +endif () + ############################################# # # Build 64bit binaries on Mac and target Macos 10.7 or later @@ -80,20 +96,6 @@ else() SET(CMAKE_BUILD_TYPE Release CACHE STRING "Choose the type of build, options are: None(CMAKE_CXX_FLAGS or CMAKE_C_FLAGS used) Debug Release RelWithDebInfo MinSizeRel.") endif() -#include (CMakeTestCCompiler) -#include (CheckCSourceCompiles) -#include (CheckCXXSourceCompiles) -#include (CheckStructHasMember) -#include (CheckLibraryExists) -#include (CheckFunctionExists) -#include (CheckCCompilerFlag) -#include (CheckCSourceRuns) -#include (CheckSymbolExists) -#include (CheckTypeSize) - -option(OPTION_TARGET_MESSAGING "Messaging (requires libcurl)" off) -option(OPTION_TARGET_DOCS "Generate Doxygen documentation" off) -option(OPTION_TEST_WITH_LOCALHOST "Use localhost as server rather than vcell.cam.uchc.edu" off) if (${OPTION_TARGET_DOCS}) # if (DOXYGEN_FOUND AND ${CMAKE_VERSION} GREATER_EQUAL 3.9) diff --git a/IDAWin/CMakeLists.txt b/IDAWin/CMakeLists.txt index 32a3a3801..a677ed793 100644 --- a/IDAWin/CMakeLists.txt +++ b/IDAWin/CMakeLists.txt @@ -29,7 +29,28 @@ set (EXE_SRC_FILES ) add_library(IDAWin STATIC ${SRC_FILES} ${HEADER_FILES}) -target_link_libraries(IDAWin sundials ExpressionParser vcellmessaging argparse spdlog::spdlog) # if MinGW is enabled, add `$<$:ws2_32>` +if (OPTION_STATICALLY_LINK) + target_link_libraries( + IDAWin -static + sundials -static + ExpressionParser -static + vcellmessaging -static + argparse -static + spdlog::spdlog -static + # if MinGW is enabled, add `$<$:ws2_32>` + ) +else () + target_link_libraries( + IDAWin + sundials + ExpressionParser + vcellmessaging + argparse + spdlog::spdlog + # if MinGW is enabled, add `$<$:ws2_32>` + ) +endif () + set(EXE_FILE SundialsSolverStandalone) if (ARCH_64bit) set(EXE_FILE ${EXE_FILE}_x64) From 313158dd83ee293c6d6943c17eead6771e95c258 Mon Sep 17 00:00:00 2001 From: Logan Drescher Date: Wed, 17 Dec 2025 13:52:59 -0500 Subject: [PATCH 25/34] Upgrading cmake to allow for all sanatizers + removed extra folder uploaded --- .github/workflows/cd.yml | 3 ++- CMakeLists.txt | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index f44c660e0..dcb8131a8 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -368,7 +368,8 @@ jobs: lipo -archs binaries/universal/${entry} done mv binaries/universal binaries/mac64 - tar czvf binaries/macos-universal.tgz binaries/mac64 + cd binaries + tar czvf macos-universal.tgz mac64 - name: Inspect binaries directory run: | diff --git a/CMakeLists.txt b/CMakeLists.txt index 9a001958b..6c9581dca 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,7 +3,7 @@ # Top Level CMake File # ############################################## -cmake_minimum_required(VERSION 3.14) +cmake_minimum_required(VERSION 3.16) project(vcell-ode-numerics) set(CMAKE_CXX_STANDARD 20) if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang") From baf7586fd65a6a7b7633f4e8567e41e26b1be5cb Mon Sep 17 00:00:00 2001 From: Logan Drescher Date: Thu, 18 Dec 2025 13:05:52 -0500 Subject: [PATCH 26/34] removed int-overflow in test --- tests/unit/message_processing_test.cpp | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/tests/unit/message_processing_test.cpp b/tests/unit/message_processing_test.cpp index 5b7463cf4..1932291fc 100644 --- a/tests/unit/message_processing_test.cpp +++ b/tests/unit/message_processing_test.cpp @@ -13,13 +13,13 @@ static std::vector eventTracker; void appendNextIndex(const WorkerEvent* event); std::vector generateFibonacci(int length); -long sequenceComparison(const std::vector &sequence1, const std::vector &sequence2); +long long sequenceComparison(const std::vector &sequence1, const std::vector &sequence2); TEST(MessageProcessingTest, MessagesAreProcessed) { try { MessageEventManager eventManager{[](WorkerEvent* event)->void{appendNextIndex(event);}}; - const int NUM_ITERATIONS = 100; + const int NUM_ITERATIONS = 50; for (int i = 0; i < NUM_ITERATIONS; ++i) { JobEvent::Status status; switch (i + 1) { @@ -45,8 +45,8 @@ void appendNextIndex(const WorkerEvent* event) { } else if (1 == eventTracker.size()) { eventTracker.emplace_back("1"); } else { - const long firstValue = std::stol(eventTracker[eventTracker.size() - 2]); - const long secondValue = std::stol(eventTracker[eventTracker.size() - 1]); + const long long firstValue = std::stoll(eventTracker[eventTracker.size() - 2]); + const long long secondValue = std::stoll(eventTracker[eventTracker.size() - 1]); const std::string nextValue{std::to_string(firstValue + secondValue)}; eventTracker.push_back(nextValue); } @@ -61,20 +61,20 @@ std::vector generateFibonacci(const int length) { for (int i = 0; i < length; ++i) { if (i == 0) { sequence.emplace_back("0"); continue; } if (i == 1) { sequence.emplace_back("1"); continue; } - const long firstValue = std::stol(sequence[sequence.size() - 2]); - const long secondValue = std::stol(sequence[sequence.size() - 1]); + const long long firstValue = std::stoll(sequence[sequence.size() - 2]); + const long long secondValue = std::stoll(sequence[sequence.size() - 1]); const std::string nextValue{std::to_string(firstValue + secondValue)}; sequence.push_back(nextValue); } return sequence; } -long sequenceComparison(const std::vector &sequence1, const std::vector &sequence2) { +long long sequenceComparison(const std::vector &sequence1, const std::vector &sequence2) { if (const size_t comp = sequence2.size() - sequence1.size(); comp) return static_cast(comp); for (size_t i = 0; i < sequence1.size(); ++i) { - const long firstValue = std::stol(sequence1[i]); - const long secondValue = std::stol(sequence2[i]); - if (const long comp = secondValue - firstValue; comp) return comp; + const long long firstValue = std::stoll(sequence1[i]); + const long long secondValue = std::stoll(sequence2[i]); + if (const long long comp = secondValue - firstValue; comp) return comp; } return 0; } \ No newline at end of file From deecdbc56bfd85f17df8e260df563c87c181cb2c Mon Sep 17 00:00:00 2001 From: Logan Drescher Date: Wed, 22 Apr 2026 09:58:46 -0400 Subject: [PATCH 27/34] Add pybind11 --- extern/pybind11/.appveyor.yml | 35 + extern/pybind11/.clang-format | 38 + extern/pybind11/.clang-tidy | 77 + extern/pybind11/.cmake-format.yaml | 73 + extern/pybind11/.codespell-ignore-lines | 24 + extern/pybind11/.gitattributes | 1 + extern/pybind11/.github/CODEOWNERS | 9 + extern/pybind11/.github/CONTRIBUTING.md | 388 +++ .../.github/ISSUE_TEMPLATE/bug-report.yml | 61 + .../.github/ISSUE_TEMPLATE/config.yml | 8 + extern/pybind11/.github/dependabot.yml | 15 + extern/pybind11/.github/labeler.yml | 8 + extern/pybind11/.github/labeler_merged.yml | 3 + extern/pybind11/.github/matchers/pylint.json | 32 + .../pybind11/.github/pull_request_template.md | 19 + extern/pybind11/.github/workflows/ci.yml | 1202 +++++++ .../pybind11/.github/workflows/configure.yml | 92 + extern/pybind11/.github/workflows/format.yml | 60 + extern/pybind11/.github/workflows/labeler.yml | 25 + extern/pybind11/.github/workflows/pip.yml | 114 + .../pybind11/.github/workflows/upstream.yml | 116 + extern/pybind11/.gitignore | 46 + extern/pybind11/.pre-commit-config.yaml | 155 + extern/pybind11/.readthedocs.yml | 20 + extern/pybind11/CMakeLists.txt | 373 ++ extern/pybind11/LICENSE | 29 + extern/pybind11/MANIFEST.in | 6 + extern/pybind11/README.rst | 181 + extern/pybind11/SECURITY.md | 13 + extern/pybind11/docs/Doxyfile | 21 + extern/pybind11/docs/_static/css/custom.css | 3 + extern/pybind11/docs/advanced/cast/chrono.rst | 81 + extern/pybind11/docs/advanced/cast/custom.rst | 93 + extern/pybind11/docs/advanced/cast/eigen.rst | 310 ++ .../docs/advanced/cast/functional.rst | 109 + extern/pybind11/docs/advanced/cast/index.rst | 43 + .../pybind11/docs/advanced/cast/overview.rst | 170 + extern/pybind11/docs/advanced/cast/stl.rst | 249 ++ .../pybind11/docs/advanced/cast/strings.rst | 296 ++ extern/pybind11/docs/advanced/classes.rst | 1335 ++++++++ extern/pybind11/docs/advanced/embedding.rst | 262 ++ extern/pybind11/docs/advanced/exceptions.rst | 401 +++ extern/pybind11/docs/advanced/functions.rst | 614 ++++ extern/pybind11/docs/advanced/misc.rst | 429 +++ extern/pybind11/docs/advanced/pycpp/index.rst | 13 + extern/pybind11/docs/advanced/pycpp/numpy.rst | 455 +++ .../pybind11/docs/advanced/pycpp/object.rst | 286 ++ .../docs/advanced/pycpp/utilities.rst | 155 + extern/pybind11/docs/advanced/smart_ptrs.rst | 174 + extern/pybind11/docs/basics.rst | 307 ++ extern/pybind11/docs/benchmark.py | 87 + extern/pybind11/docs/benchmark.rst | 95 + extern/pybind11/docs/changelog.rst | 3006 +++++++++++++++++ extern/pybind11/docs/classes.rst | 555 +++ extern/pybind11/docs/cmake/index.rst | 8 + extern/pybind11/docs/compiling.rst | 649 ++++ extern/pybind11/docs/conf.py | 368 ++ extern/pybind11/docs/faq.rst | 308 ++ extern/pybind11/docs/index.rst | 48 + extern/pybind11/docs/installing.rst | 105 + extern/pybind11/docs/limitations.rst | 72 + extern/pybind11/docs/pybind11-logo.png | Bin 0 -> 61034 bytes .../docs/pybind11_vs_boost_python1.png | Bin 0 -> 44653 bytes .../docs/pybind11_vs_boost_python1.svg | 427 +++ .../docs/pybind11_vs_boost_python2.png | Bin 0 -> 41121 bytes .../docs/pybind11_vs_boost_python2.svg | 427 +++ extern/pybind11/docs/reference.rst | 130 + extern/pybind11/docs/release.rst | 143 + extern/pybind11/docs/requirements.txt | 6 + extern/pybind11/docs/upgrade.rst | 594 ++++ extern/pybind11/include/pybind11/attr.h | 690 ++++ .../pybind11/include/pybind11/buffer_info.h | 208 ++ extern/pybind11/include/pybind11/cast.h | 1837 ++++++++++ extern/pybind11/include/pybind11/chrono.h | 225 ++ extern/pybind11/include/pybind11/common.h | 2 + extern/pybind11/include/pybind11/complex.h | 74 + .../pybind11/include/pybind11/detail/class.h | 748 ++++ .../pybind11/include/pybind11/detail/common.h | 1267 +++++++ .../pybind11/include/pybind11/detail/descr.h | 171 + .../pybind11/include/pybind11/detail/init.h | 434 +++ .../include/pybind11/detail/internals.h | 667 ++++ .../pybind11/detail/type_caster_base.h | 1218 +++++++ .../pybind11/include/pybind11/detail/typeid.h | 65 + extern/pybind11/include/pybind11/eigen.h | 12 + .../pybind11/include/pybind11/eigen/common.h | 9 + .../pybind11/include/pybind11/eigen/matrix.h | 714 ++++ .../pybind11/include/pybind11/eigen/tensor.h | 517 +++ extern/pybind11/include/pybind11/embed.h | 316 ++ extern/pybind11/include/pybind11/eval.h | 156 + extern/pybind11/include/pybind11/functional.h | 138 + extern/pybind11/include/pybind11/gil.h | 247 ++ .../include/pybind11/gil_safe_call_once.h | 91 + extern/pybind11/include/pybind11/iostream.h | 265 ++ extern/pybind11/include/pybind11/numpy.h | 2133 ++++++++++++ extern/pybind11/include/pybind11/operators.h | 202 ++ extern/pybind11/include/pybind11/options.h | 92 + extern/pybind11/include/pybind11/pybind11.h | 2963 ++++++++++++++++ extern/pybind11/include/pybind11/pytypes.h | 2574 ++++++++++++++ extern/pybind11/include/pybind11/stl.h | 448 +++ .../include/pybind11/stl/filesystem.h | 116 + extern/pybind11/include/pybind11/stl_bind.h | 823 +++++ .../pybind11/type_caster_pyobject_ptr.h | 61 + extern/pybind11/include/pybind11/typing.h | 125 + extern/pybind11/noxfile.py | 107 + extern/pybind11/pybind11/__init__.py | 17 + extern/pybind11/pybind11/__main__.py | 62 + extern/pybind11/pybind11/_version.py | 12 + extern/pybind11/pybind11/commands.py | 37 + extern/pybind11/pybind11/py.typed | 0 extern/pybind11/pybind11/setup_helpers.py | 500 +++ extern/pybind11/pyproject.toml | 95 + extern/pybind11/setup.cfg | 43 + extern/pybind11/setup.py | 150 + extern/pybind11/tests/CMakeLists.txt | 589 ++++ extern/pybind11/tests/conftest.py | 222 ++ extern/pybind11/tests/constructor_stats.h | 322 ++ .../pybind11/tests/cross_module_gil_utils.cpp | 108 + ...s_module_interleaved_error_already_set.cpp | 51 + .../tests/eigen_tensor_avoid_stl_array.cpp | 14 + extern/pybind11/tests/env.py | 27 + .../tests/extra_python_package/pytest.ini | 0 .../tests/extra_python_package/test_files.py | 293 ++ .../tests/extra_setuptools/pytest.ini | 0 .../extra_setuptools/test_setuphelper.py | 151 + extern/pybind11/tests/local_bindings.h | 92 + extern/pybind11/tests/object.h | 205 ++ .../tests/pybind11_cross_module_tests.cpp | 149 + extern/pybind11/tests/pybind11_tests.cpp | 129 + extern/pybind11/tests/pybind11_tests.h | 85 + extern/pybind11/tests/pytest.ini | 22 + extern/pybind11/tests/requirements.txt | 15 + extern/pybind11/tests/test_async.cpp | 25 + extern/pybind11/tests/test_async.py | 24 + extern/pybind11/tests/test_buffers.cpp | 259 ++ extern/pybind11/tests/test_buffers.py | 228 ++ .../pybind11/tests/test_builtin_casters.cpp | 392 +++ extern/pybind11/tests/test_builtin_casters.py | 528 +++ extern/pybind11/tests/test_call_policies.cpp | 115 + extern/pybind11/tests/test_call_policies.py | 247 ++ extern/pybind11/tests/test_callbacks.cpp | 280 ++ extern/pybind11/tests/test_callbacks.py | 225 ++ extern/pybind11/tests/test_chrono.cpp | 81 + extern/pybind11/tests/test_chrono.py | 205 ++ extern/pybind11/tests/test_class.cpp | 657 ++++ extern/pybind11/tests/test_class.py | 499 +++ .../tests/test_cmake_build/CMakeLists.txt | 80 + .../pybind11/tests/test_cmake_build/embed.cpp | 23 + .../installed_embed/CMakeLists.txt | 28 + .../installed_function/CMakeLists.txt | 39 + .../installed_target/CMakeLists.txt | 46 + .../pybind11/tests/test_cmake_build/main.cpp | 6 + .../subdirectory_embed/CMakeLists.txt | 47 + .../subdirectory_function/CMakeLists.txt | 41 + .../subdirectory_target/CMakeLists.txt | 47 + .../pybind11/tests/test_cmake_build/test.py | 8 + extern/pybind11/tests/test_const_name.cpp | 55 + extern/pybind11/tests/test_const_name.py | 29 + .../tests/test_constants_and_functions.cpp | 158 + .../tests/test_constants_and_functions.py | 56 + extern/pybind11/tests/test_copy_move.cpp | 533 +++ extern/pybind11/tests/test_copy_move.py | 132 + .../tests/test_custom_type_casters.cpp | 221 ++ .../tests/test_custom_type_casters.py | 122 + .../pybind11/tests/test_custom_type_setup.cpp | 41 + .../pybind11/tests/test_custom_type_setup.py | 48 + .../pybind11/tests/test_docstring_options.cpp | 141 + .../pybind11/tests/test_docstring_options.py | 64 + extern/pybind11/tests/test_eigen_matrix.cpp | 445 +++ extern/pybind11/tests/test_eigen_matrix.py | 814 +++++ extern/pybind11/tests/test_eigen_tensor.cpp | 18 + extern/pybind11/tests/test_eigen_tensor.inl | 333 ++ extern/pybind11/tests/test_eigen_tensor.py | 288 ++ .../pybind11/tests/test_embed/CMakeLists.txt | 47 + extern/pybind11/tests/test_embed/catch.cpp | 43 + .../tests/test_embed/external_module.cpp | 20 + .../tests/test_embed/test_interpreter.cpp | 488 +++ .../tests/test_embed/test_interpreter.py | 14 + .../tests/test_embed/test_trampoline.py | 16 + extern/pybind11/tests/test_enum.cpp | 133 + extern/pybind11/tests/test_enum.py | 269 ++ extern/pybind11/tests/test_eval.cpp | 118 + extern/pybind11/tests/test_eval.py | 50 + extern/pybind11/tests/test_eval_call.py | 4 + extern/pybind11/tests/test_exceptions.cpp | 388 +++ extern/pybind11/tests/test_exceptions.h | 13 + extern/pybind11/tests/test_exceptions.py | 432 +++ .../tests/test_factory_constructors.cpp | 430 +++ .../tests/test_factory_constructors.py | 516 +++ extern/pybind11/tests/test_gil_scoped.cpp | 144 + extern/pybind11/tests/test_gil_scoped.py | 242 ++ extern/pybind11/tests/test_iostream.cpp | 126 + extern/pybind11/tests/test_iostream.py | 291 ++ .../tests/test_kwargs_and_defaults.cpp | 327 ++ .../tests/test_kwargs_and_defaults.py | 425 +++ extern/pybind11/tests/test_local_bindings.cpp | 106 + extern/pybind11/tests/test_local_bindings.py | 257 ++ .../tests/test_methods_and_attributes.cpp | 493 +++ .../tests/test_methods_and_attributes.py | 537 +++ extern/pybind11/tests/test_modules.cpp | 125 + extern/pybind11/tests/test_modules.py | 116 + .../tests/test_multiple_inheritance.cpp | 341 ++ .../tests/test_multiple_inheritance.py | 493 +++ extern/pybind11/tests/test_numpy_array.cpp | 552 +++ extern/pybind11/tests/test_numpy_array.py | 674 ++++ extern/pybind11/tests/test_numpy_dtypes.cpp | 639 ++++ extern/pybind11/tests/test_numpy_dtypes.py | 448 +++ .../pybind11/tests/test_numpy_vectorize.cpp | 107 + extern/pybind11/tests/test_numpy_vectorize.py | 266 ++ extern/pybind11/tests/test_opaque_types.cpp | 77 + extern/pybind11/tests/test_opaque_types.py | 58 + .../tests/test_operator_overloading.cpp | 281 ++ .../tests/test_operator_overloading.py | 151 + extern/pybind11/tests/test_pickling.cpp | 194 ++ extern/pybind11/tests/test_pickling.py | 93 + .../test_python_multiple_inheritance.cpp | 45 + .../tests/test_python_multiple_inheritance.py | 35 + extern/pybind11/tests/test_pytypes.cpp | 846 +++++ extern/pybind11/tests/test_pytypes.py | 954 ++++++ .../tests/test_sequences_and_iterators.cpp | 600 ++++ .../tests/test_sequences_and_iterators.py | 265 ++ extern/pybind11/tests/test_smart_ptr.cpp | 473 +++ extern/pybind11/tests/test_smart_ptr.py | 315 ++ extern/pybind11/tests/test_stl.cpp | 551 +++ extern/pybind11/tests/test_stl.py | 381 +++ extern/pybind11/tests/test_stl_binders.cpp | 276 ++ extern/pybind11/tests/test_stl_binders.py | 393 +++ .../tests/test_tagbased_polymorphic.cpp | 147 + .../tests/test_tagbased_polymorphic.py | 28 + extern/pybind11/tests/test_thread.cpp | 66 + extern/pybind11/tests/test_thread.py | 42 + .../tests/test_type_caster_pyobject_ptr.cpp | 130 + .../tests/test_type_caster_pyobject_ptr.py | 104 + extern/pybind11/tests/test_union.cpp | 22 + extern/pybind11/tests/test_union.py | 8 + .../tests/test_unnamed_namespace_a.cpp | 38 + .../tests/test_unnamed_namespace_a.py | 34 + .../tests/test_unnamed_namespace_b.cpp | 13 + .../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 | 592 ++++ .../pybind11/tests/test_virtual_functions.py | 458 +++ .../pybind11/tests/valgrind-numpy-scipy.supp | 140 + extern/pybind11/tests/valgrind-python.supp | 117 + extern/pybind11/tools/FindCatch.cmake | 76 + extern/pybind11/tools/FindEigen3.cmake | 86 + extern/pybind11/tools/FindPythonLibsNew.cmake | 310 ++ extern/pybind11/tools/JoinPaths.cmake | 23 + extern/pybind11/tools/check-style.sh | 44 + .../pybind11/tools/cmake_uninstall.cmake.in | 23 + .../codespell_ignore_lines_from_errors.py | 39 + extern/pybind11/tools/libsize.py | 36 + extern/pybind11/tools/make_changelog.py | 90 + extern/pybind11/tools/pybind11.pc.in | 7 + extern/pybind11/tools/pybind11Common.cmake | 419 +++ extern/pybind11/tools/pybind11Config.cmake.in | 233 ++ extern/pybind11/tools/pybind11NewTools.cmake | 311 ++ extern/pybind11/tools/pybind11Tools.cmake | 239 ++ extern/pybind11/tools/pyproject.toml | 3 + extern/pybind11/tools/setup_global.py.in | 63 + extern/pybind11/tools/setup_main.py.in | 44 + 261 files changed, 66526 insertions(+) create mode 100644 extern/pybind11/.appveyor.yml create mode 100644 extern/pybind11/.clang-format create mode 100644 extern/pybind11/.clang-tidy create mode 100644 extern/pybind11/.cmake-format.yaml create mode 100644 extern/pybind11/.codespell-ignore-lines create mode 100644 extern/pybind11/.gitattributes create mode 100644 extern/pybind11/.github/CODEOWNERS create mode 100644 extern/pybind11/.github/CONTRIBUTING.md create mode 100644 extern/pybind11/.github/ISSUE_TEMPLATE/bug-report.yml create mode 100644 extern/pybind11/.github/ISSUE_TEMPLATE/config.yml create mode 100644 extern/pybind11/.github/dependabot.yml create mode 100644 extern/pybind11/.github/labeler.yml create mode 100644 extern/pybind11/.github/labeler_merged.yml create mode 100644 extern/pybind11/.github/matchers/pylint.json create mode 100644 extern/pybind11/.github/pull_request_template.md create mode 100644 extern/pybind11/.github/workflows/ci.yml create mode 100644 extern/pybind11/.github/workflows/configure.yml create mode 100644 extern/pybind11/.github/workflows/format.yml create mode 100644 extern/pybind11/.github/workflows/labeler.yml create mode 100644 extern/pybind11/.github/workflows/pip.yml create mode 100644 extern/pybind11/.github/workflows/upstream.yml create mode 100644 extern/pybind11/.gitignore create mode 100644 extern/pybind11/.pre-commit-config.yaml create mode 100644 extern/pybind11/.readthedocs.yml create mode 100644 extern/pybind11/CMakeLists.txt create mode 100644 extern/pybind11/LICENSE create mode 100644 extern/pybind11/MANIFEST.in create mode 100644 extern/pybind11/README.rst create mode 100644 extern/pybind11/SECURITY.md create mode 100644 extern/pybind11/docs/Doxyfile create mode 100644 extern/pybind11/docs/_static/css/custom.css create mode 100644 extern/pybind11/docs/advanced/cast/chrono.rst create mode 100644 extern/pybind11/docs/advanced/cast/custom.rst create mode 100644 extern/pybind11/docs/advanced/cast/eigen.rst create mode 100644 extern/pybind11/docs/advanced/cast/functional.rst create mode 100644 extern/pybind11/docs/advanced/cast/index.rst create mode 100644 extern/pybind11/docs/advanced/cast/overview.rst create mode 100644 extern/pybind11/docs/advanced/cast/stl.rst create mode 100644 extern/pybind11/docs/advanced/cast/strings.rst create mode 100644 extern/pybind11/docs/advanced/classes.rst create mode 100644 extern/pybind11/docs/advanced/embedding.rst create mode 100644 extern/pybind11/docs/advanced/exceptions.rst create mode 100644 extern/pybind11/docs/advanced/functions.rst create mode 100644 extern/pybind11/docs/advanced/misc.rst create mode 100644 extern/pybind11/docs/advanced/pycpp/index.rst create mode 100644 extern/pybind11/docs/advanced/pycpp/numpy.rst create mode 100644 extern/pybind11/docs/advanced/pycpp/object.rst create mode 100644 extern/pybind11/docs/advanced/pycpp/utilities.rst create mode 100644 extern/pybind11/docs/advanced/smart_ptrs.rst create mode 100644 extern/pybind11/docs/basics.rst create mode 100644 extern/pybind11/docs/benchmark.py create mode 100644 extern/pybind11/docs/benchmark.rst create mode 100644 extern/pybind11/docs/changelog.rst create mode 100644 extern/pybind11/docs/classes.rst create mode 100644 extern/pybind11/docs/cmake/index.rst create mode 100644 extern/pybind11/docs/compiling.rst create mode 100644 extern/pybind11/docs/conf.py create mode 100644 extern/pybind11/docs/faq.rst create mode 100644 extern/pybind11/docs/index.rst create mode 100644 extern/pybind11/docs/installing.rst create mode 100644 extern/pybind11/docs/limitations.rst create mode 100644 extern/pybind11/docs/pybind11-logo.png create mode 100644 extern/pybind11/docs/pybind11_vs_boost_python1.png create mode 100644 extern/pybind11/docs/pybind11_vs_boost_python1.svg create mode 100644 extern/pybind11/docs/pybind11_vs_boost_python2.png create mode 100644 extern/pybind11/docs/pybind11_vs_boost_python2.svg create mode 100644 extern/pybind11/docs/reference.rst create mode 100644 extern/pybind11/docs/release.rst create mode 100644 extern/pybind11/docs/requirements.txt create mode 100644 extern/pybind11/docs/upgrade.rst create mode 100644 extern/pybind11/include/pybind11/attr.h create mode 100644 extern/pybind11/include/pybind11/buffer_info.h create mode 100644 extern/pybind11/include/pybind11/cast.h create mode 100644 extern/pybind11/include/pybind11/chrono.h create mode 100644 extern/pybind11/include/pybind11/common.h create mode 100644 extern/pybind11/include/pybind11/complex.h create mode 100644 extern/pybind11/include/pybind11/detail/class.h create mode 100644 extern/pybind11/include/pybind11/detail/common.h create mode 100644 extern/pybind11/include/pybind11/detail/descr.h create mode 100644 extern/pybind11/include/pybind11/detail/init.h create mode 100644 extern/pybind11/include/pybind11/detail/internals.h create mode 100644 extern/pybind11/include/pybind11/detail/type_caster_base.h create mode 100644 extern/pybind11/include/pybind11/detail/typeid.h create mode 100644 extern/pybind11/include/pybind11/eigen.h create mode 100644 extern/pybind11/include/pybind11/eigen/common.h create mode 100644 extern/pybind11/include/pybind11/eigen/matrix.h create mode 100644 extern/pybind11/include/pybind11/eigen/tensor.h create mode 100644 extern/pybind11/include/pybind11/embed.h create mode 100644 extern/pybind11/include/pybind11/eval.h create mode 100644 extern/pybind11/include/pybind11/functional.h create mode 100644 extern/pybind11/include/pybind11/gil.h create mode 100644 extern/pybind11/include/pybind11/gil_safe_call_once.h create mode 100644 extern/pybind11/include/pybind11/iostream.h create mode 100644 extern/pybind11/include/pybind11/numpy.h create mode 100644 extern/pybind11/include/pybind11/operators.h create mode 100644 extern/pybind11/include/pybind11/options.h create mode 100644 extern/pybind11/include/pybind11/pybind11.h create mode 100644 extern/pybind11/include/pybind11/pytypes.h create mode 100644 extern/pybind11/include/pybind11/stl.h create mode 100644 extern/pybind11/include/pybind11/stl/filesystem.h create mode 100644 extern/pybind11/include/pybind11/stl_bind.h create mode 100644 extern/pybind11/include/pybind11/type_caster_pyobject_ptr.h create mode 100644 extern/pybind11/include/pybind11/typing.h create mode 100644 extern/pybind11/noxfile.py create mode 100644 extern/pybind11/pybind11/__init__.py create mode 100644 extern/pybind11/pybind11/__main__.py create mode 100644 extern/pybind11/pybind11/_version.py create mode 100644 extern/pybind11/pybind11/commands.py create mode 100644 extern/pybind11/pybind11/py.typed create mode 100644 extern/pybind11/pybind11/setup_helpers.py create mode 100644 extern/pybind11/pyproject.toml create mode 100644 extern/pybind11/setup.cfg create mode 100644 extern/pybind11/setup.py create mode 100644 extern/pybind11/tests/CMakeLists.txt create mode 100644 extern/pybind11/tests/conftest.py create mode 100644 extern/pybind11/tests/constructor_stats.h create mode 100644 extern/pybind11/tests/cross_module_gil_utils.cpp create mode 100644 extern/pybind11/tests/cross_module_interleaved_error_already_set.cpp create mode 100644 extern/pybind11/tests/eigen_tensor_avoid_stl_array.cpp create mode 100644 extern/pybind11/tests/env.py create mode 100644 extern/pybind11/tests/extra_python_package/pytest.ini create mode 100644 extern/pybind11/tests/extra_python_package/test_files.py create mode 100644 extern/pybind11/tests/extra_setuptools/pytest.ini create mode 100644 extern/pybind11/tests/extra_setuptools/test_setuphelper.py create mode 100644 extern/pybind11/tests/local_bindings.h create mode 100644 extern/pybind11/tests/object.h create mode 100644 extern/pybind11/tests/pybind11_cross_module_tests.cpp create mode 100644 extern/pybind11/tests/pybind11_tests.cpp create mode 100644 extern/pybind11/tests/pybind11_tests.h create mode 100644 extern/pybind11/tests/pytest.ini create mode 100644 extern/pybind11/tests/requirements.txt create mode 100644 extern/pybind11/tests/test_async.cpp create mode 100644 extern/pybind11/tests/test_async.py create mode 100644 extern/pybind11/tests/test_buffers.cpp create mode 100644 extern/pybind11/tests/test_buffers.py create mode 100644 extern/pybind11/tests/test_builtin_casters.cpp create mode 100644 extern/pybind11/tests/test_builtin_casters.py create mode 100644 extern/pybind11/tests/test_call_policies.cpp create mode 100644 extern/pybind11/tests/test_call_policies.py create mode 100644 extern/pybind11/tests/test_callbacks.cpp create mode 100644 extern/pybind11/tests/test_callbacks.py create mode 100644 extern/pybind11/tests/test_chrono.cpp create mode 100644 extern/pybind11/tests/test_chrono.py create mode 100644 extern/pybind11/tests/test_class.cpp create mode 100644 extern/pybind11/tests/test_class.py create mode 100644 extern/pybind11/tests/test_cmake_build/CMakeLists.txt create mode 100644 extern/pybind11/tests/test_cmake_build/embed.cpp create mode 100644 extern/pybind11/tests/test_cmake_build/installed_embed/CMakeLists.txt create mode 100644 extern/pybind11/tests/test_cmake_build/installed_function/CMakeLists.txt create mode 100644 extern/pybind11/tests/test_cmake_build/installed_target/CMakeLists.txt create mode 100644 extern/pybind11/tests/test_cmake_build/main.cpp create mode 100644 extern/pybind11/tests/test_cmake_build/subdirectory_embed/CMakeLists.txt create mode 100644 extern/pybind11/tests/test_cmake_build/subdirectory_function/CMakeLists.txt create mode 100644 extern/pybind11/tests/test_cmake_build/subdirectory_target/CMakeLists.txt create mode 100644 extern/pybind11/tests/test_cmake_build/test.py create mode 100644 extern/pybind11/tests/test_const_name.cpp create mode 100644 extern/pybind11/tests/test_const_name.py create mode 100644 extern/pybind11/tests/test_constants_and_functions.cpp create mode 100644 extern/pybind11/tests/test_constants_and_functions.py create mode 100644 extern/pybind11/tests/test_copy_move.cpp create mode 100644 extern/pybind11/tests/test_copy_move.py create mode 100644 extern/pybind11/tests/test_custom_type_casters.cpp create mode 100644 extern/pybind11/tests/test_custom_type_casters.py create mode 100644 extern/pybind11/tests/test_custom_type_setup.cpp create mode 100644 extern/pybind11/tests/test_custom_type_setup.py create mode 100644 extern/pybind11/tests/test_docstring_options.cpp create mode 100644 extern/pybind11/tests/test_docstring_options.py create mode 100644 extern/pybind11/tests/test_eigen_matrix.cpp create mode 100644 extern/pybind11/tests/test_eigen_matrix.py create mode 100644 extern/pybind11/tests/test_eigen_tensor.cpp create mode 100644 extern/pybind11/tests/test_eigen_tensor.inl create mode 100644 extern/pybind11/tests/test_eigen_tensor.py create mode 100644 extern/pybind11/tests/test_embed/CMakeLists.txt create mode 100644 extern/pybind11/tests/test_embed/catch.cpp create mode 100644 extern/pybind11/tests/test_embed/external_module.cpp create mode 100644 extern/pybind11/tests/test_embed/test_interpreter.cpp create mode 100644 extern/pybind11/tests/test_embed/test_interpreter.py create mode 100644 extern/pybind11/tests/test_embed/test_trampoline.py create mode 100644 extern/pybind11/tests/test_enum.cpp create mode 100644 extern/pybind11/tests/test_enum.py create mode 100644 extern/pybind11/tests/test_eval.cpp create mode 100644 extern/pybind11/tests/test_eval.py create mode 100644 extern/pybind11/tests/test_eval_call.py create mode 100644 extern/pybind11/tests/test_exceptions.cpp create mode 100644 extern/pybind11/tests/test_exceptions.h create mode 100644 extern/pybind11/tests/test_exceptions.py create mode 100644 extern/pybind11/tests/test_factory_constructors.cpp create mode 100644 extern/pybind11/tests/test_factory_constructors.py create mode 100644 extern/pybind11/tests/test_gil_scoped.cpp create mode 100644 extern/pybind11/tests/test_gil_scoped.py create mode 100644 extern/pybind11/tests/test_iostream.cpp create mode 100644 extern/pybind11/tests/test_iostream.py create mode 100644 extern/pybind11/tests/test_kwargs_and_defaults.cpp create mode 100644 extern/pybind11/tests/test_kwargs_and_defaults.py create mode 100644 extern/pybind11/tests/test_local_bindings.cpp create mode 100644 extern/pybind11/tests/test_local_bindings.py create mode 100644 extern/pybind11/tests/test_methods_and_attributes.cpp create mode 100644 extern/pybind11/tests/test_methods_and_attributes.py create mode 100644 extern/pybind11/tests/test_modules.cpp create mode 100644 extern/pybind11/tests/test_modules.py create mode 100644 extern/pybind11/tests/test_multiple_inheritance.cpp create mode 100644 extern/pybind11/tests/test_multiple_inheritance.py create mode 100644 extern/pybind11/tests/test_numpy_array.cpp create mode 100644 extern/pybind11/tests/test_numpy_array.py create mode 100644 extern/pybind11/tests/test_numpy_dtypes.cpp create mode 100644 extern/pybind11/tests/test_numpy_dtypes.py create mode 100644 extern/pybind11/tests/test_numpy_vectorize.cpp create mode 100644 extern/pybind11/tests/test_numpy_vectorize.py create mode 100644 extern/pybind11/tests/test_opaque_types.cpp create mode 100644 extern/pybind11/tests/test_opaque_types.py create mode 100644 extern/pybind11/tests/test_operator_overloading.cpp create mode 100644 extern/pybind11/tests/test_operator_overloading.py create mode 100644 extern/pybind11/tests/test_pickling.cpp create mode 100644 extern/pybind11/tests/test_pickling.py create mode 100644 extern/pybind11/tests/test_python_multiple_inheritance.cpp create mode 100644 extern/pybind11/tests/test_python_multiple_inheritance.py create mode 100644 extern/pybind11/tests/test_pytypes.cpp create mode 100644 extern/pybind11/tests/test_pytypes.py create mode 100644 extern/pybind11/tests/test_sequences_and_iterators.cpp create mode 100644 extern/pybind11/tests/test_sequences_and_iterators.py create mode 100644 extern/pybind11/tests/test_smart_ptr.cpp create mode 100644 extern/pybind11/tests/test_smart_ptr.py create mode 100644 extern/pybind11/tests/test_stl.cpp create mode 100644 extern/pybind11/tests/test_stl.py create mode 100644 extern/pybind11/tests/test_stl_binders.cpp create mode 100644 extern/pybind11/tests/test_stl_binders.py create mode 100644 extern/pybind11/tests/test_tagbased_polymorphic.cpp create mode 100644 extern/pybind11/tests/test_tagbased_polymorphic.py create mode 100644 extern/pybind11/tests/test_thread.cpp create mode 100644 extern/pybind11/tests/test_thread.py create mode 100644 extern/pybind11/tests/test_type_caster_pyobject_ptr.cpp create mode 100644 extern/pybind11/tests/test_type_caster_pyobject_ptr.py create mode 100644 extern/pybind11/tests/test_union.cpp create mode 100644 extern/pybind11/tests/test_union.py create mode 100644 extern/pybind11/tests/test_unnamed_namespace_a.cpp create mode 100644 extern/pybind11/tests/test_unnamed_namespace_a.py create mode 100644 extern/pybind11/tests/test_unnamed_namespace_b.cpp create mode 100644 extern/pybind11/tests/test_unnamed_namespace_b.py create mode 100644 extern/pybind11/tests/test_vector_unique_ptr_member.cpp create mode 100644 extern/pybind11/tests/test_vector_unique_ptr_member.py create mode 100644 extern/pybind11/tests/test_virtual_functions.cpp create mode 100644 extern/pybind11/tests/test_virtual_functions.py create mode 100644 extern/pybind11/tests/valgrind-numpy-scipy.supp create mode 100644 extern/pybind11/tests/valgrind-python.supp create mode 100644 extern/pybind11/tools/FindCatch.cmake create mode 100644 extern/pybind11/tools/FindEigen3.cmake create mode 100644 extern/pybind11/tools/FindPythonLibsNew.cmake create mode 100644 extern/pybind11/tools/JoinPaths.cmake create mode 100755 extern/pybind11/tools/check-style.sh create mode 100644 extern/pybind11/tools/cmake_uninstall.cmake.in create mode 100644 extern/pybind11/tools/codespell_ignore_lines_from_errors.py create mode 100644 extern/pybind11/tools/libsize.py create mode 100755 extern/pybind11/tools/make_changelog.py create mode 100644 extern/pybind11/tools/pybind11.pc.in create mode 100644 extern/pybind11/tools/pybind11Common.cmake create mode 100644 extern/pybind11/tools/pybind11Config.cmake.in create mode 100644 extern/pybind11/tools/pybind11NewTools.cmake create mode 100644 extern/pybind11/tools/pybind11Tools.cmake create mode 100644 extern/pybind11/tools/pyproject.toml create mode 100644 extern/pybind11/tools/setup_global.py.in create mode 100644 extern/pybind11/tools/setup_main.py.in diff --git a/extern/pybind11/.appveyor.yml b/extern/pybind11/.appveyor.yml new file mode 100644 index 000000000..360760ac8 --- /dev/null +++ b/extern/pybind11/.appveyor.yml @@ -0,0 +1,35 @@ +version: 1.0.{build} +image: +- Visual Studio 2017 +test: off +skip_branch_with_pr: true +build: + parallel: true +platform: +- x86 +environment: + matrix: + - PYTHON: 36 + CONFIG: Debug +install: +- ps: | + $env:CMAKE_GENERATOR = "Visual Studio 15 2017" + if ($env:PLATFORM -eq "x64") { $env:PYTHON = "$env:PYTHON-x64" } + $env:PATH = "C:\Python$env:PYTHON\;C:\Python$env:PYTHON\Scripts\;$env:PATH" + python -W ignore -m pip install --upgrade pip wheel + python -W ignore -m pip install pytest numpy --no-warn-script-location pytest-timeout +- ps: | + Start-FileDownload 'https://gitlab.com/libeigen/eigen/-/archive/3.3.7/eigen-3.3.7.zip' + 7z x eigen-3.3.7.zip -y > $null + $env:CMAKE_INCLUDE_PATH = "eigen-3.3.7;$env:CMAKE_INCLUDE_PATH" +build_script: +- cmake -G "%CMAKE_GENERATOR%" -A "%CMAKE_ARCH%" + -DCMAKE_CXX_STANDARD=14 + -DPYBIND11_WERROR=ON + -DDOWNLOAD_CATCH=ON + -DCMAKE_SUPPRESS_REGENERATION=1 + . +- set MSBuildLogger="C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll" +- cmake --build . --config %CONFIG% --target pytest -- /m /v:m /logger:%MSBuildLogger% +- cmake --build . --config %CONFIG% --target cpptest -- /m /v:m /logger:%MSBuildLogger% +on_failure: if exist "tests\test_cmake_build" type tests\test_cmake_build\*.log* diff --git a/extern/pybind11/.clang-format b/extern/pybind11/.clang-format new file mode 100644 index 000000000..b477a1603 --- /dev/null +++ b/extern/pybind11/.clang-format @@ -0,0 +1,38 @@ +--- +# See all possible options and defaults with: +# clang-format --style=llvm --dump-config +BasedOnStyle: LLVM +AccessModifierOffset: -4 +AllowShortLambdasOnASingleLine: true +AlwaysBreakTemplateDeclarations: Yes +BinPackArguments: false +BinPackParameters: false +BreakBeforeBinaryOperators: All +BreakConstructorInitializers: BeforeColon +ColumnLimit: 99 +CommentPragmas: 'NOLINT:.*|^ IWYU pragma:' +IncludeBlocks: Regroup +IndentCaseLabels: true +IndentPPDirectives: AfterHash +IndentWidth: 4 +Language: Cpp +SpaceAfterCStyleCast: true +Standard: Cpp11 +StatementMacros: ['PyObject_HEAD'] +TabWidth: 4 +IncludeCategories: + - Regex: '' + Priority: 4 + - Regex: '.*' + Priority: 5 +... diff --git a/extern/pybind11/.clang-tidy b/extern/pybind11/.clang-tidy new file mode 100644 index 000000000..23018386c --- /dev/null +++ b/extern/pybind11/.clang-tidy @@ -0,0 +1,77 @@ +FormatStyle: file + +Checks: | + *bugprone*, + *performance*, + clang-analyzer-optin.cplusplus.VirtualCall, + clang-analyzer-optin.performance.Padding, + cppcoreguidelines-init-variables, + cppcoreguidelines-prefer-member-initializer, + cppcoreguidelines-pro-type-static-cast-downcast, + cppcoreguidelines-slicing, + google-explicit-constructor, + llvm-namespace-comment, + misc-definitions-in-headers, + misc-misplaced-const, + misc-non-copyable-objects, + misc-static-assert, + misc-throw-by-value-catch-by-reference, + misc-uniqueptr-reset-release, + misc-unused-parameters, + modernize-avoid-bind, + modernize-loop-convert, + modernize-make-shared, + modernize-redundant-void-arg, + modernize-replace-auto-ptr, + modernize-replace-disallow-copy-and-assign-macro, + modernize-replace-random-shuffle, + modernize-shrink-to-fit, + modernize-use-auto, + modernize-use-bool-literals, + modernize-use-default-member-init, + modernize-use-emplace, + modernize-use-equals-default, + modernize-use-equals-delete, + modernize-use-noexcept, + modernize-use-nullptr, + modernize-use-override, + modernize-use-using, + readability-avoid-const-params-in-decls, + readability-braces-around-statements, + readability-const-return-type, + readability-container-size-empty, + readability-delete-null-pointer, + readability-else-after-return, + readability-implicit-bool-conversion, + readability-inconsistent-declaration-parameter-name, + readability-make-member-function-const, + readability-misplaced-array-index, + readability-non-const-parameter, + readability-qualified-auto, + readability-redundant-function-ptr-dereference, + readability-redundant-smartptr-get, + readability-redundant-string-cstr, + readability-simplify-subscript-expr, + readability-static-accessed-through-instance, + readability-static-definition-in-anonymous-namespace, + readability-string-compare, + readability-suspicious-call-argument, + readability-uniqueptr-delete-release, + -bugprone-easily-swappable-parameters, + -bugprone-exception-escape, + -bugprone-reserved-identifier, + -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 + value: true +- key: performance-unnecessary-value-param.AllowedTypes + value: 'exception_ptr$;' +- key: readability-implicit-bool-conversion.AllowPointerConditions + value: true + +HeaderFilterRegex: 'pybind11/.*h' diff --git a/extern/pybind11/.cmake-format.yaml b/extern/pybind11/.cmake-format.yaml new file mode 100644 index 000000000..a2a69f3f8 --- /dev/null +++ b/extern/pybind11/.cmake-format.yaml @@ -0,0 +1,73 @@ +parse: + additional_commands: + pybind11_add_module: + flags: + - THIN_LTO + - MODULE + - SHARED + - NO_EXTRAS + - EXCLUDE_FROM_ALL + - SYSTEM + +format: + line_width: 99 + tab_size: 2 + + # If an argument group contains more than this many sub-groups + # (parg or kwarg groups) then force it to a vertical layout. + max_subgroups_hwrap: 2 + + # If a positional argument group contains more than this many + # arguments, then force it to a vertical layout. + max_pargs_hwrap: 6 + + # If a cmdline positional group consumes more than this many + # lines without nesting, then invalidate the layout (and nest) + max_rows_cmdline: 2 + separate_ctrl_name_with_space: false + separate_fn_name_with_space: false + dangle_parens: false + + # If the trailing parenthesis must be 'dangled' on its on + # 'line, then align it to this reference: `prefix`: the start' + # 'of the statement, `prefix-indent`: the start of the' + # 'statement, plus one indentation level, `child`: align to' + # the column of the arguments + dangle_align: prefix + # If the statement spelling length (including space and + # parenthesis) is smaller than this amount, then force reject + # nested layouts. + min_prefix_chars: 4 + + # If the statement spelling length (including space and + # parenthesis) is larger than the tab width by more than this + # amount, then force reject un-nested layouts. + max_prefix_chars: 10 + + # If a candidate layout is wrapped horizontally but it exceeds + # this many lines, then reject the layout. + max_lines_hwrap: 2 + + line_ending: unix + + # Format command names consistently as 'lower' or 'upper' case + command_case: canonical + + # Format keywords consistently as 'lower' or 'upper' case + # unchanged is valid too + keyword_case: 'upper' + + # A list of command names which should always be wrapped + always_wrap: [] + + # If true, the argument lists which are known to be sortable + # will be sorted lexicographically + enable_sort: true + + # If true, the parsers may infer whether or not an argument + # list is sortable (without annotation). + autosort: false + +# Causes a few issues - can be solved later, possibly. +markup: + enable_markup: false diff --git a/extern/pybind11/.codespell-ignore-lines b/extern/pybind11/.codespell-ignore-lines new file mode 100644 index 000000000..2a01d63eb --- /dev/null +++ b/extern/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/extern/pybind11/.gitattributes b/extern/pybind11/.gitattributes new file mode 100644 index 000000000..d611e1496 --- /dev/null +++ b/extern/pybind11/.gitattributes @@ -0,0 +1 @@ +docs/*.svg binary diff --git a/extern/pybind11/.github/CODEOWNERS b/extern/pybind11/.github/CODEOWNERS new file mode 100644 index 000000000..4e2c66902 --- /dev/null +++ b/extern/pybind11/.github/CODEOWNERS @@ -0,0 +1,9 @@ +*.cmake @henryiii +CMakeLists.txt @henryiii +*.yml @henryiii +*.yaml @henryiii +/tools/ @henryiii +/pybind11/ @henryiii +noxfile.py @henryiii +.clang-format @henryiii +.clang-tidy @henryiii diff --git a/extern/pybind11/.github/CONTRIBUTING.md b/extern/pybind11/.github/CONTRIBUTING.md new file mode 100644 index 000000000..f5a08e2d7 --- /dev/null +++ b/extern/pybind11/.github/CONTRIBUTING.md @@ -0,0 +1,388 @@ +Thank you for your interest in this project! Please refer to the following +sections on how to contribute code and bug reports. + +### Reporting bugs + +Before submitting a question or bug report, please take a moment of your time +and ensure that your issue isn't already discussed in the project documentation +provided at [pybind11.readthedocs.org][] or in the [issue tracker][]. You can +also check [gitter][] to see if it came up before. + +Assuming that you have identified a previously unknown problem or an important +question, it's essential that you submit a self-contained and minimal piece of +code that reproduces the problem. In other words: 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 in isolation; or +ideally make a small PR with a failing test case that can be used as a starting +point. + +## Pull requests + +Contributions are submitted, reviewed, and accepted using GitHub pull requests. +Please refer to [this article][using pull requests] for details and adhere to +the following rules to make the process as smooth as possible: + +* Make a new branch for every feature you're working on. +* Make small and clean pull requests that are easy to review but make sure they + do add value by themselves. +* Add tests for any new functionality and run the test suite (`cmake --build + build --target pytest`) to ensure that no existing features break. +* Please run [`pre-commit`][pre-commit] to check your code matches the + project style. (Note that `gawk` is required.) Use `pre-commit run + --all-files` before committing (or use installed-mode, check pre-commit docs) + to verify your code passes before pushing to save time. +* This project has a strong focus on providing general solutions using a + minimal amount of code, thus small pull requests are greatly preferred. + +### Licensing of contributions + +pybind11 is provided under a BSD-style license that can be found in the +``LICENSE`` file. By using, distributing, or contributing to this project, you +agree to the terms and conditions of this license. + +You are under no obligation whatsoever to provide any bug fixes, patches, or +upgrades to the features, functionality or performance of the source code +("Enhancements") to anyone; however, if you choose to make your Enhancements +available either publicly, or directly to the author of this software, without +imposing a separate written license agreement for such Enhancements, then you +hereby grant the following license: a non-exclusive, royalty-free perpetual +license to install, use, modify, prepare derivative works, incorporate into +other computer software, distribute, and sublicense such enhancements or +derivative works thereof, in binary and source code form. + + +## Development of pybind11 + +### Quick setup + +To setup a quick development environment, use [`nox`](https://nox.thea.codes). +This will allow you to do some common tasks with minimal setup effort, but will +take more time to run and be less flexible than a full development environment. +If you use [`pipx run nox`](https://pipx.pypa.io), you don't even need to +install `nox`. Examples: + +```bash +# List all available sessions +nox -l + +# Run linters +nox -s lint + +# Run tests on Python 3.9 +nox -s tests-3.9 + +# Build and preview docs +nox -s docs -- serve + +# Build SDists and wheels +nox -s build +``` + +### Full setup + +To setup an ideal development environment, run the following commands on a +system with CMake 3.14+: + +```bash +python3 -m venv venv +source venv/bin/activate +pip install -r tests/requirements.txt +cmake -S . -B build -DDOWNLOAD_CATCH=ON -DDOWNLOAD_EIGEN=ON +cmake --build build -j4 +``` + +Tips: + +* You can use `virtualenv` (faster, from PyPI) instead of `venv`. +* You can select any name for your environment folder; if it contains "env" it + will be ignored by git. +* If you don't have CMake 3.14+, just add "cmake" to the pip install command. +* You can use `-DPYBIND11_FINDPYTHON=ON` to use FindPython on CMake 3.12+ +* In classic mode, you may need to set `-DPYTHON_EXECUTABLE=/path/to/python`. + FindPython uses `-DPython_ROOT_DIR=/path/to` or + `-DPython_EXECUTABLE=/path/to/python`. + +### Configuration options + +In CMake, configuration options are given with "-D". Options are stored in the +build directory, in the `CMakeCache.txt` file, so they are remembered for each +build directory. Two selections are special - the generator, given with `-G`, +and the compiler, which is selected based on environment variables `CXX` and +similar, or `-DCMAKE_CXX_COMPILER=`. Unlike the others, these cannot be changed +after the initial run. + +The valid options are: + +* `-DCMAKE_BUILD_TYPE`: Release, Debug, MinSizeRel, RelWithDebInfo +* `-DPYBIND11_FINDPYTHON=ON`: Use CMake 3.12+'s FindPython instead of the + classic, deprecated, custom FindPythonLibs +* `-DPYBIND11_NOPYTHON=ON`: Disable all Python searching (disables tests) +* `-DBUILD_TESTING=ON`: Enable the tests +* `-DDOWNLOAD_CATCH=ON`: Download catch to build the C++ tests +* `-DDOWNLOAD_EIGEN=ON`: Download Eigen for the NumPy tests +* `-DPYBIND11_INSTALL=ON/OFF`: Enable the install target (on by default for the + master project) +* `-DUSE_PYTHON_INSTALL_DIR=ON`: Try to install into the python dir + + +
A few standard CMake tricks: (click to expand)

+ +* Use `cmake --build build -v` to see the commands used to build the files. +* Use `cmake build -LH` to list the CMake options with help. +* Use `ccmake` if available to see a curses (terminal) gui, or `cmake-gui` for + a completely graphical interface (not present in the PyPI package). +* Use `cmake --build build -j12` to build with 12 cores (for example). +* Use `-G` and the name of a generator to use something different. `cmake + --help` lists the generators available. + - On Unix, setting `CMAKE_GENERATER=Ninja` in your environment will give + you automatic multithreading on all your CMake projects! +* Open the `CMakeLists.txt` with QtCreator to generate for that IDE. +* You can use `-DCMAKE_EXPORT_COMPILE_COMMANDS=ON` to generate the `.json` file + that some tools expect. + +

+ + +To run the tests, you can "build" the check target: + +```bash +cmake --build build --target check +``` + +`--target` can be spelled `-t` in CMake 3.15+. You can also run individual +tests with these targets: + +* `pytest`: Python tests only, using the +[pytest](https://docs.pytest.org/en/stable/) framework +* `cpptest`: C++ tests only +* `test_cmake_build`: Install / subdirectory tests + +If you want to build just a subset of tests, use +`-DPYBIND11_TEST_OVERRIDE="test_callbacks;test_pickling"`. If this is +empty, all tests will be built. Tests are specified without an extension if they need both a .py and +.cpp file. + +You may also pass flags to the `pytest` target by editing `tests/pytest.ini` or +by using the `PYTEST_ADDOPTS` environment variable +(see [`pytest` docs](https://docs.pytest.org/en/2.7.3/customize.html#adding-default-options)). As an example: + +```bash +env PYTEST_ADDOPTS="--capture=no --exitfirst" \ + cmake --build build --target pytest +# Or using abbreviated flags +env PYTEST_ADDOPTS="-s -x" cmake --build build --target pytest +``` + +### Formatting + +All formatting is handled by pre-commit. + +Install with brew (macOS) or pip (any OS): + +```bash +# Any OS +python3 -m pip install pre-commit + +# OR macOS with homebrew: +brew install pre-commit +``` + +Then, you can run it on the items you've added to your staging area, or all +files: + +```bash +pre-commit run +# OR +pre-commit run --all-files +``` + +And, if you want to always use it, you can install it as a git hook (hence the +name, pre-commit): + +```bash +pre-commit install +``` + +### Clang-Format + +As of v2.6.2, pybind11 ships with a [`clang-format`][clang-format] +configuration file at the top level of the repo (the filename is +`.clang-format`). Currently, formatting is NOT applied automatically, but +manually using `clang-format` for newly developed files is highly encouraged. +To check if a file needs formatting: + +```bash +clang-format -style=file --dry-run some.cpp +``` + +The output will show things to be fixed, if any. To actually format the file: + +```bash +clang-format -style=file -i some.cpp +``` + +Note that the `-style-file` option searches the parent directories for the +`.clang-format` file, i.e. the commands above can be run in any subdirectory +of the pybind11 repo. + +### Clang-Tidy + +[`clang-tidy`][clang-tidy] performs deeper static code analyses and is +more complex to run, compared to `clang-format`, but support for `clang-tidy` +is built into the pybind11 CMake configuration. To run `clang-tidy`, the +following recipe should work. Run the `docker` command from the top-level +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: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 +``` + +You can add `--fix` to the options list if you want. + +### Include what you use + +To run include what you use, install (`brew install include-what-you-use` on +macOS), then run: + +```bash +cmake -S . -B build-iwyu -DCMAKE_CXX_INCLUDE_WHAT_YOU_USE=$(which include-what-you-use) +cmake --build build +``` + +The report is sent to stderr; you can pipe it into a file if you wish. + +### Build recipes + +This builds with the Intel compiler (assuming it is in your path, along with a +recent CMake and Python): + +```bash +python3 -m venv venv +. venv/bin/activate +pip install pytest +cmake -S . -B build-intel -DCMAKE_CXX_COMPILER=$(which icpc) -DDOWNLOAD_CATCH=ON -DDOWNLOAD_EIGEN=ON -DPYBIND11_WERROR=ON +``` + +This will test the PGI compilers: + +```bash +docker run --rm -it -v $PWD:/pybind11 nvcr.io/hpc/pgi-compilers:ce +apt-get update && apt-get install -y python3-dev python3-pip python3-pytest +wget -qO- "https://cmake.org/files/v3.18/cmake-3.18.2-Linux-x86_64.tar.gz" | tar --strip-components=1 -xz -C /usr/local +cmake -S pybind11/ -B build +cmake --build build +``` + +### Explanation of the SDist/wheel building design + +> These details below are _only_ for packaging the Python sources from git. The +> SDists and wheels created do not have any extra requirements at all and are +> completely normal. + +The main objective of the packaging system is to create SDists (Python's source +distribution packages) and wheels (Python's binary distribution packages) that +include everything that is needed to work with pybind11, and which can be +installed without any additional dependencies. This is more complex than it +appears: in order to support CMake as a first class language even when using +the PyPI package, they must include the _generated_ CMake files (so as not to +require CMake when installing the `pybind11` package itself). They should also +provide the option to install to the "standard" location +(`/include/pybind11` and `/share/cmake/pybind11`) so they are +easy to find with CMake, but this can cause problems if you are not an +environment or using ``pyproject.toml`` requirements. This was solved by having +two packages; the "nice" pybind11 package that stores the includes and CMake +files inside the package, that you get access to via functions in the package, +and a `pybind11-global` package that can be included via `pybind11[global]` if +you want the more invasive but discoverable file locations. + +If you want to install or package the GitHub source, it is best to have Pip 10 +or newer on Windows, macOS, or Linux (manylinux1 compatible, includes most +distributions). You can then build the SDists, or run any procedure that makes +SDists internally, like making wheels or installing. + + +```bash +# Editable development install example +python3 -m pip install -e . +``` + +Since Pip itself does not have an `sdist` command (it does have `wheel` and +`install`), you may want to use the upcoming `build` package: + +```bash +python3 -m pip install build + +# Normal package +python3 -m build -s . + +# Global extra +PYBIND11_GLOBAL_SDIST=1 python3 -m build -s . +``` + +If you want to use the classic "direct" usage of `python setup.py`, you will +need CMake 3.15+ and either `make` or `ninja` preinstalled (possibly via `pip +install cmake ninja`), since directly running Python on `setup.py` cannot pick +up and install `pyproject.toml` requirements. As long as you have those two +things, though, everything works the way you would expect: + +```bash +# Normal package +python3 setup.py sdist + +# Global extra +PYBIND11_GLOBAL_SDIST=1 python3 setup.py sdist +``` + +A detailed explanation of the build procedure design for developers wanting to +work on or maintain the packaging system is as follows: + +#### 1. Building from the source directory + +When you invoke any `setup.py` command from the source directory, including +`pip wheel .` and `pip install .`, you will activate a full source build. This +is made of the following steps: + +1. If the tool is PEP 518 compliant, like Pip 10+, it will create a temporary + virtual environment and install the build requirements (mostly CMake) into + it. (if you are not on Windows, macOS, or a manylinux compliant system, you + can disable this with `--no-build-isolation` as long as you have CMake 3.15+ + installed) +2. The environment variable `PYBIND11_GLOBAL_SDIST` is checked - if it is set + and truthy, this will be make the accessory `pybind11-global` package, + instead of the normal `pybind11` package. This package is used for + installing the files directly to your environment root directory, using + `pybind11[global]`. +2. `setup.py` reads the version from `pybind11/_version.py` and verifies it + matches `includes/pybind11/detail/common.h`. +3. CMake is run with `-DCMAKE_INSTALL_PREIFX=pybind11`. Since the CMake install + procedure uses only relative paths and is identical on all platforms, these + files are valid as long as they stay in the correct relative position to the + includes. `pybind11/share/cmake/pybind11` has the CMake files, and + `pybind11/include` has the includes. The build directory is discarded. +4. Simpler files are placed in the SDist: `tools/setup_*.py.in`, + `tools/pyproject.toml` (`main` or `global`) +5. The package is created by running the setup function in the + `tools/setup_*.py`. `setup_main.py` fills in Python packages, and + `setup_global.py` fills in only the data/header slots. +6. A context manager cleans up the temporary CMake install directory (even if + an error is thrown). + +### 2. Building from SDist + +Since the SDist has the rendered template files in `tools` along with the +includes and CMake files in the correct locations, the builds are completely +trivial and simple. No extra requirements are required. You can even use Pip 9 +if you really want to. + + +[pre-commit]: https://pre-commit.com +[clang-format]: https://clang.llvm.org/docs/ClangFormat.html +[clang-tidy]: https://clang.llvm.org/extra/clang-tidy/ +[pybind11.readthedocs.org]: http://pybind11.readthedocs.org/en/latest +[issue tracker]: https://github.com/pybind/pybind11/issues +[gitter]: https://gitter.im/pybind/Lobby +[using pull requests]: https://help.github.com/articles/using-pull-requests diff --git a/extern/pybind11/.github/ISSUE_TEMPLATE/bug-report.yml b/extern/pybind11/.github/ISSUE_TEMPLATE/bug-report.yml new file mode 100644 index 000000000..4f1e78f33 --- /dev/null +++ b/extern/pybind11/.github/ISSUE_TEMPLATE/bug-report.yml @@ -0,0 +1,61 @@ +name: Bug Report +description: File an issue about a bug +title: "[BUG]: " +labels: [triage] +body: + - type: markdown + attributes: + value: | + 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: + label: Required prerequisites + description: Make sure you've completed the following steps before submitting your issue -- thank you! + options: + - label: Make sure you've read the [documentation](https://pybind11.readthedocs.io). Your issue may be addressed there. + required: true + - label: Search the [issue tracker](https://github.com/pybind/pybind11/issues) and [Discussions](https:/pybind/pybind11/discussions) to verify that this hasn't already been reported. +1 or comment there if it has. + required: true + - 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: + label: Problem description + placeholder: >- + Provide a short description, state the expected behavior and what + actually happens. Include relevant information like what version of + pybind11 you are using, what system you are on, and any useful commands + / output. + validations: + required: true + + - type: textarea + id: code + attributes: + label: Reproducible example code + placeholder: >- + 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. — 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/extern/pybind11/.github/ISSUE_TEMPLATE/config.yml b/extern/pybind11/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 000000000..27f9a8044 --- /dev/null +++ b/extern/pybind11/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,8 @@ +blank_issues_enabled: false +contact_links: + - name: Ask a question + url: https://github.com/pybind/pybind11/discussions/new + about: Please ask and answer questions here, or propose new ideas. + - name: Gitter room + url: https://gitter.im/pybind/Lobby + about: A room for discussing pybind11 with an active community diff --git a/extern/pybind11/.github/dependabot.yml b/extern/pybind11/.github/dependabot.yml new file mode 100644 index 000000000..22c34bd74 --- /dev/null +++ b/extern/pybind11/.github/dependabot.yml @@ -0,0 +1,15 @@ +version: 2 +updates: + # Maintain dependencies for GitHub Actions + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + groups: + actions: + patterns: + - "*" + ignore: + - dependency-name: actions/checkout + versions: + - "<5" diff --git a/extern/pybind11/.github/labeler.yml b/extern/pybind11/.github/labeler.yml new file mode 100644 index 000000000..abb0d05aa --- /dev/null +++ b/extern/pybind11/.github/labeler.yml @@ -0,0 +1,8 @@ +docs: +- any: + - 'docs/**/*.rst' + - '!docs/changelog.rst' + - '!docs/upgrade.rst' + +ci: +- '.github/workflows/*.yml' diff --git a/extern/pybind11/.github/labeler_merged.yml b/extern/pybind11/.github/labeler_merged.yml new file mode 100644 index 000000000..2374ad42e --- /dev/null +++ b/extern/pybind11/.github/labeler_merged.yml @@ -0,0 +1,3 @@ +needs changelog: +- all: + - '!docs/changelog.rst' diff --git a/extern/pybind11/.github/matchers/pylint.json b/extern/pybind11/.github/matchers/pylint.json new file mode 100644 index 000000000..e3a6bd16b --- /dev/null +++ b/extern/pybind11/.github/matchers/pylint.json @@ -0,0 +1,32 @@ +{ + "problemMatcher": [ + { + "severity": "warning", + "pattern": [ + { + "regexp": "^([^:]+):(\\d+):(\\d+): ([A-DF-Z]\\d+): \\033\\[[\\d;]+m([^\\033]+).*$", + "file": 1, + "line": 2, + "column": 3, + "code": 4, + "message": 5 + } + ], + "owner": "pylint-warning" + }, + { + "severity": "error", + "pattern": [ + { + "regexp": "^([^:]+):(\\d+):(\\d+): (E\\d+): \\033\\[[\\d;]+m([^\\033]+).*$", + "file": 1, + "line": 2, + "column": 3, + "code": 4, + "message": 5 + } + ], + "owner": "pylint-error" + } + ] +} diff --git a/extern/pybind11/.github/pull_request_template.md b/extern/pybind11/.github/pull_request_template.md new file mode 100644 index 000000000..54b7f5100 --- /dev/null +++ b/extern/pybind11/.github/pull_request_template.md @@ -0,0 +1,19 @@ + +## Description + + + + +## Suggested changelog entry: + + + +```rst + +``` + + diff --git a/extern/pybind11/.github/workflows/ci.yml b/extern/pybind11/.github/workflows/ci.yml new file mode 100644 index 000000000..5cc6c3515 --- /dev/null +++ b/extern/pybind11/.github/workflows/ci.yml @@ -0,0 +1,1202 @@ +name: CI + +on: + workflow_dispatch: + pull_request: + push: + branches: + - master + - 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 + # versions of default compilers and Python versions in GitHub Actions. + standard: + strategy: + fail-fast: false + matrix: + runs-on: [ubuntu-20.04, windows-2022, macos-latest] + python: + - '3.6' + - '3.9' + - '3.10' + - '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 + # existing keys match. + # + # We support an optional key: args, for cmake args + include: + # Just add a key + - runs-on: ubuntu-20.04 + python: '3.6' + args: > + -DPYBIND11_FINDPYTHON=ON + -DCMAKE_CXX_FLAGS="-D_=1" + - runs-on: ubuntu-20.04 + python: 'pypy-3.8' + args: > + -DPYBIND11_FINDPYTHON=ON + - runs-on: windows-2019 + python: '3.6' + args: > + -DPYBIND11_FINDPYTHON=ON + # Inject a couple Windows 2019 runs + - runs-on: windows-2019 + python: '3.9' + + name: "🐍 ${{ matrix.python }} • ${{ matrix.runs-on }} • x64 ${{ matrix.args }}" + runs-on: ${{ matrix.runs-on }} + + steps: + - uses: actions/checkout@v4 + + - name: Setup Python ${{ matrix.python }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python }} + + - name: Setup Boost (Linux) + # Can't use boost + define _ + if: runner.os == 'Linux' && matrix.python != '3.6' + run: sudo apt-get install libboost-dev + + - name: Setup Boost (macOS) + if: runner.os == 'macOS' + run: brew install boost + + - name: Update CMake + uses: jwlawson/actions-setup-cmake@v2.0 + + - name: Cache wheels + if: runner.os == 'macOS' + uses: actions/cache@v4 + with: + # This path is specific to macOS - we really only need it for PyPy NumPy wheels + # See https://github.com/actions/cache/blob/master/examples.md#python---pip + # for ways to do this more generally + path: ~/Library/Caches/pip + # Look to see if there is a cache hit for the corresponding requirements file + key: ${{ runner.os }}-pip-${{ matrix.python }}-x64-${{ hashFiles('tests/requirements.txt') }} + + - name: Prepare env + run: | + 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 + + # First build - C++11 mode and inplace + # More-or-less randomly adding -DPYBIND11_SIMPLE_GIL_MANAGEMENT=ON here + # (same for PYBIND11_NUMPY_1_ONLY, but requires a NumPy 1.x at runtime). + - name: Configure C++11 ${{ matrix.args }} + run: > + cmake -S . -B . + -DPYBIND11_WERROR=ON + -DPYBIND11_DISABLE_HANDLE_TYPE_NAME_DEFAULT_IMPLEMENTATION=ON + -DPYBIND11_SIMPLE_GIL_MANAGEMENT=ON + -DPYBIND11_NUMPY_1_ONLY=ON + -DDOWNLOAD_CATCH=ON + -DDOWNLOAD_EIGEN=ON + -DCMAKE_CXX_STANDARD=11 + ${{ matrix.args }} + + - name: Build C++11 + run: cmake --build . -j 2 + + - name: Python tests C++11 + run: cmake --build . --target pytest -j 2 + + - 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' || matrix.python == 'pypy-3.8'))" + run: cmake --build . --target cpptest -j 2 + + - name: Interface test C++11 + run: cmake --build . --target test_cmake_build + + - name: Clean directory + 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. + # (same for PYBIND11_NUMPY_1_ONLY, but requires a NumPy 1.x at runtime). + - name: Configure C++17 + run: > + cmake -S . -B build2 + -DPYBIND11_WERROR=ON + -DPYBIND11_SIMPLE_GIL_MANAGEMENT=OFF + -DPYBIND11_NUMPY_1_ONLY=ON + -DDOWNLOAD_CATCH=ON + -DDOWNLOAD_EIGEN=ON + -DCMAKE_CXX_STANDARD=17 + ${{ matrix.args }} + + - name: Build + run: cmake --build build2 -j 2 + + - name: Python tests + run: cmake --build build2 --target pytest + + - 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' || matrix.python == 'pypy-3.8'))" + run: cmake --build build2 --target cpptest + + # Third build - C++17 mode with unstable ABI + - name: Configure (unstable ABI) + run: > + cmake -S . -B build3 + -DPYBIND11_WERROR=ON + -DDOWNLOAD_CATCH=ON + -DDOWNLOAD_EIGEN=ON + -DCMAKE_CXX_STANDARD=17 + -DPYBIND11_INTERNALS_VERSION=10000000 + ${{ matrix.args }} + + - name: Build (unstable ABI) + run: cmake --build build3 -j 2 + + - name: Python tests (unstable ABI) + run: cmake --build build3 --target pytest + + - name: Interface test + run: cmake --build build2 --target test_cmake_build + + # This makes sure the setup_helpers module can build packages using + # setuptools + - name: Setuptools helpers test + run: | + pip install setuptools + pytest tests/extra_setuptools + if: "!(matrix.runs-on == 'windows-2022')" + + + deadsnakes: + strategy: + fail-fast: false + matrix: + include: + # TODO: Fails on 3.10, investigate + # JOB DISABLED (NEEDS WORK): https://github.com/pybind/pybind11/issues/4889 + # - python-version: "3.9" + # python-debug: true + # valgrind: true + - python-version: "3.11" + python-debug: false + + name: "🐍 ${{ matrix.python-version }}${{ matrix.python-debug && '-dbg' || '' }} (deadsnakes)${{ matrix.valgrind && ' • Valgrind' || '' }} • x64" + runs-on: ubuntu-20.04 + + steps: + - uses: actions/checkout@v4 + + - name: Setup Python ${{ matrix.python-version }} (deadsnakes) + uses: deadsnakes/action@v3.1.0 + with: + python-version: ${{ matrix.python-version }} + debug: ${{ matrix.python-debug }} + + - name: Update CMake + uses: jwlawson/actions-setup-cmake@v2.0 + + - name: Valgrind cache + if: matrix.valgrind + uses: actions/cache@v4 + id: cache-valgrind + with: + path: valgrind + key: 3.16.1 # Valgrind version + + - name: Compile Valgrind + if: matrix.valgrind && steps.cache-valgrind.outputs.cache-hit != 'true' + run: | + VALGRIND_VERSION=3.16.1 + curl https://sourceware.org/pub/valgrind/valgrind-$VALGRIND_VERSION.tar.bz2 -o - | tar xj + mv valgrind-$VALGRIND_VERSION valgrind + cd valgrind + ./configure + make -j 2 > /dev/null + + - name: Install Valgrind + if: matrix.valgrind + working-directory: valgrind + run: | + sudo make install + sudo apt-get update + sudo apt-get install libc6-dbg # Needed by Valgrind + + - name: Prepare env + run: | + python -m pip install -r tests/requirements.txt + + - name: Configure + run: > + cmake -S . -B build + -DCMAKE_BUILD_TYPE=Debug + -DPYBIND11_WERROR=ON + -DDOWNLOAD_CATCH=ON + -DDOWNLOAD_EIGEN=ON + -DCMAKE_CXX_STANDARD=17 + + - name: Build + run: cmake --build build -j 2 + + - name: Python tests + run: cmake --build build --target pytest + + - name: C++ tests + run: cmake --build build --target cpptest + + - name: Run Valgrind on Python tests + if: matrix.valgrind + run: cmake --build build --target memcheck + + + # Testing on clang using the excellent silkeh clang docker images + clang: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + clang: + - 3.6 + - 3.7 + - 3.9 + - 7 + - 9 + - dev + std: + - 11 + container_suffix: + - "" + include: + - clang: 5 + std: 14 + - 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" + - clang: 16 + std: 20 + container_suffix: "-bullseye" + + name: "🐍 3 • Clang ${{ matrix.clang }} • C++${{ matrix.std }} • x64" + container: "silkeh/clang:${{ matrix.clang }}${{ matrix.container_suffix }}" + + steps: + - uses: actions/checkout@v4 + + - name: Add wget and python3 + run: apt-get update && apt-get install -y python3-dev python3-numpy python3-pytest libeigen3-dev + + - name: Configure + shell: bash + run: > + cmake -S . -B build + -DPYBIND11_WERROR=ON + -DDOWNLOAD_CATCH=ON + -DCMAKE_CXX_STANDARD=${{ matrix.std }} + -DPYTHON_EXECUTABLE=$(python3 -c "import sys; print(sys.executable)") + + - name: Build + run: cmake --build build -j 2 + + - name: Python tests + run: cmake --build build --target pytest + + - name: C++ tests + run: cmake --build build --target cpptest + + - name: Interface test + run: cmake --build build --target test_cmake_build + + + # Testing NVCC; forces sources to behave like .cu files + cuda: + runs-on: ubuntu-latest + name: "🐍 3.10 • CUDA 12.2 • Ubuntu 22.04" + container: nvidia/cuda:12.2.0-devel-ubuntu22.04 + + steps: + - uses: actions/checkout@v4 + + # tzdata will try to ask for the timezone, so set the DEBIAN_FRONTEND + - name: Install 🐍 3 + run: apt-get update && DEBIAN_FRONTEND="noninteractive" apt-get install -y cmake git python3-dev python3-pytest python3-numpy + + - name: Configure + run: cmake -S . -B build -DPYBIND11_CUDA_TESTS=ON -DPYBIND11_WERROR=ON -DDOWNLOAD_CATCH=ON + + - name: Build + run: cmake --build build -j2 --verbose + + - name: Python tests + run: cmake --build build --target pytest + + +# TODO: Internal compiler error - report to NVidia +# # Testing CentOS 8 + PGI compilers +# centos-nvhpc8: +# runs-on: ubuntu-latest +# name: "🐍 3 • CentOS8 / PGI 20.11 • x64" +# container: centos:8 +# +# steps: +# - uses: actions/checkout@v4 +# +# - name: Add Python 3 and a few requirements +# run: yum update -y && yum install -y git python3-devel python3-numpy python3-pytest make environment-modules +# +# - name: Install CMake with pip +# run: | +# python3 -m pip install --upgrade pip +# python3 -m pip install cmake --prefer-binary +# +# - name: Install NVidia HPC SDK +# run: > +# yum -y install +# https://developer.download.nvidia.com/hpc-sdk/20.11/nvhpc-20-11-20.11-1.x86_64.rpm +# https://developer.download.nvidia.com/hpc-sdk/20.11/nvhpc-2020-20.11-1.x86_64.rpm +# +# - name: Configure +# shell: bash +# run: | +# source /etc/profile.d/modules.sh +# module load /opt/nvidia/hpc_sdk/modulefiles/nvhpc/20.11 +# cmake -S . -B build -DDOWNLOAD_CATCH=ON -DCMAKE_CXX_STANDARD=14 -DPYTHON_EXECUTABLE=$(python3 -c "import sys; print(sys.executable)") +# +# - name: Build +# run: cmake --build build -j 2 --verbose +# +# - name: Python tests +# run: cmake --build build --target pytest +# +# - name: C++ tests +# run: cmake --build build --target cpptest +# +# - name: Interface test +# run: cmake --build build --target test_cmake_build + + + # Testing on Ubuntu + NVHPC (previous PGI) compilers, which seems to require more workarounds + ubuntu-nvhpc7: + runs-on: ubuntu-20.04 + name: "🐍 3 • NVHPC 23.5 • C++17 • x64" + + env: + # tzdata will try to ask for the timezone, so set the DEBIAN_FRONTEND + DEBIAN_FRONTEND: 'noninteractive' + steps: + - uses: actions/checkout@v4 + + - name: Add NVHPC Repo + run: | + echo 'deb [trusted=yes] https://developer.download.nvidia.com/hpc-sdk/ubuntu/amd64 /' | \ + sudo tee /etc/apt/sources.list.d/nvhpc.list + + - name: Install 🐍 3 & NVHPC + run: | + sudo apt-get update -y && \ + sudo apt-get install -y cmake environment-modules git python3-dev python3-pip python3-numpy && \ + sudo apt-get install -y --no-install-recommends nvhpc-23-5 && \ + sudo rm -rf /var/lib/apt/lists/* + python3 -m pip install --upgrade pip + python3 -m pip install --upgrade pytest + + # On some systems, you many need further workarounds: + # https://github.com/pybind/pybind11/pull/2475 + - name: Configure + shell: bash + run: | + source /etc/profile.d/modules.sh + module load /opt/nvidia/hpc_sdk/modulefiles/nvhpc/23.5 + cmake -S . -B build -DDOWNLOAD_CATCH=ON \ + -DCMAKE_CXX_STANDARD=17 \ + -DPYTHON_EXECUTABLE=$(python3 -c "import sys; print(sys.executable)") \ + -DCMAKE_CXX_FLAGS="-Wc,--pending_instantiations=0" \ + -DPYBIND11_TEST_FILTER="test_smart_ptr.cpp" + + - name: Build + run: cmake --build build -j 2 --verbose + + - name: Python tests + run: cmake --build build --target pytest + + - name: C++ tests + run: cmake --build build --target cpptest + + - name: Interface test + run: cmake --build build --target test_cmake_build + + + # Testing on GCC using the GCC docker images (only recent images supported) + gcc: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + include: + - { 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 } + - { gcc: 13, std: 20 } + + name: "🐍 3 • GCC ${{ matrix.gcc }} • C++${{ matrix.std }}• x64" + container: "gcc:${{ matrix.gcc }}" + + steps: + - uses: actions/checkout@v4 + + - name: Add Python 3 + run: apt-get update; apt-get install -y python3-dev python3-numpy python3-pytest python3-pip libeigen3-dev + + - name: Update pip + run: python3 -m pip install --upgrade pip + + - name: Update CMake + uses: jwlawson/actions-setup-cmake@v2.0 + + - name: Configure + shell: bash + run: > + cmake -S . -B build + -DPYBIND11_WERROR=ON + -DDOWNLOAD_CATCH=ON + -DCMAKE_CXX_STANDARD=${{ matrix.std }} + -DPYTHON_EXECUTABLE=$(python3 -c "import sys; print(sys.executable)") + + - name: Build + run: cmake --build build -j 2 + + - name: Python tests + run: cmake --build build --target pytest + + - name: C++ tests + run: cmake --build build --target cpptest + + - 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: + runs-on: ubuntu-20.04 + + name: "🐍 3 • ICC latest • x64" + + steps: + - uses: actions/checkout@v4 + + - name: Add apt repo + run: | + sudo apt-get update + sudo apt-get install -y wget build-essential pkg-config cmake ca-certificates gnupg + wget https://apt.repos.intel.com/intel-gpg-keys/GPG-PUB-KEY-INTEL-SW-PRODUCTS-2023.PUB + sudo apt-key add GPG-PUB-KEY-INTEL-SW-PRODUCTS-2023.PUB + echo "deb https://apt.repos.intel.com/oneapi all main" | sudo tee /etc/apt/sources.list.d/oneAPI.list + + - name: Add ICC & Python 3 + run: sudo apt-get update; sudo apt-get install -y intel-oneapi-compiler-dpcpp-cpp-and-cpp-classic cmake python3-dev python3-numpy python3-pytest python3-pip + + - name: Update pip + run: | + set +e; source /opt/intel/oneapi/setvars.sh; set -e + python3 -m pip install --upgrade pip + + - name: Install dependencies + run: | + set +e; source /opt/intel/oneapi/setvars.sh; set -e + python3 -m pip install -r tests/requirements.txt + + - name: Configure C++11 + run: | + set +e; source /opt/intel/oneapi/setvars.sh; set -e + cmake -S . -B build-11 \ + -DPYBIND11_WERROR=ON \ + -DDOWNLOAD_CATCH=ON \ + -DDOWNLOAD_EIGEN=OFF \ + -DCMAKE_CXX_STANDARD=11 \ + -DCMAKE_CXX_COMPILER=$(which icpc) \ + -DPYTHON_EXECUTABLE=$(python3 -c "import sys; print(sys.executable)") + + - name: Build C++11 + run: | + set +e; source /opt/intel/oneapi/setvars.sh; set -e + cmake --build build-11 -j 2 -v + + - name: Python tests C++11 + run: | + set +e; source /opt/intel/oneapi/setvars.sh; set -e + sudo service apport stop + cmake --build build-11 --target check + + - name: C++ tests C++11 + run: | + set +e; source /opt/intel/oneapi/setvars.sh; set -e + cmake --build build-11 --target cpptest + + - name: Interface test C++11 + run: | + set +e; source /opt/intel/oneapi/setvars.sh; set -e + cmake --build build-11 --target test_cmake_build + + - name: Configure C++17 + run: | + set +e; source /opt/intel/oneapi/setvars.sh; set -e + cmake -S . -B build-17 \ + -DPYBIND11_WERROR=ON \ + -DDOWNLOAD_CATCH=ON \ + -DDOWNLOAD_EIGEN=OFF \ + -DCMAKE_CXX_STANDARD=17 \ + -DCMAKE_CXX_COMPILER=$(which icpc) \ + -DPYTHON_EXECUTABLE=$(python3 -c "import sys; print(sys.executable)") + + - name: Build C++17 + run: | + set +e; source /opt/intel/oneapi/setvars.sh; set -e + cmake --build build-17 -j 2 -v + + - name: Python tests C++17 + run: | + set +e; source /opt/intel/oneapi/setvars.sh; set -e + sudo service apport stop + cmake --build build-17 --target check + + - name: C++ tests C++17 + run: | + set +e; source /opt/intel/oneapi/setvars.sh; set -e + cmake --build build-17 --target cpptest + + - name: Interface test C++17 + run: | + set +e; source /opt/intel/oneapi/setvars.sh; set -e + cmake --build build-17 --target test_cmake_build + + + # Testing on CentOS (manylinux uses a centos base, and this is an easy way + # to get GCC 4.8, which is the manylinux1 compiler). + centos: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + container: + - "centos:7" # GCC 4.8 + - "almalinux:8" + - "almalinux:9" + + name: "🐍 3 • ${{ matrix.container }} • x64" + container: "${{ matrix.container }}" + + steps: + - name: Latest actions/checkout + uses: actions/checkout@v4 + if: matrix.container != 'centos:7' + + - name: Pin actions/checkout as required for centos:7 + uses: actions/checkout@v3 + if: matrix.container == 'centos:7' + + - name: Add Python 3 (RHEL 7) + if: matrix.container == 'centos:7' + run: yum update -y && yum install -y python3-devel gcc-c++ make git + + - name: Add Python 3 (RHEL 8+) + if: matrix.container != 'centos:7' + run: dnf update -y && dnf install -y python3-devel gcc-c++ make git + + - name: Update pip + run: python3 -m pip install --upgrade pip + + - name: Install dependencies + run: | + python3 -m pip install cmake -r tests/requirements.txt + + - name: Ensure NumPy 2 is used (required Python >= 3.9) + if: matrix.container == 'almalinux:9' + run: | + python3 -m pip install 'numpy>=2.0.0b1' 'scipy>=1.13.0rc1' + + - name: Configure + shell: bash + run: > + cmake -S . -B build + -DCMAKE_BUILD_TYPE=MinSizeRel + -DPYBIND11_WERROR=ON + -DDOWNLOAD_CATCH=ON + -DDOWNLOAD_EIGEN=ON + -DCMAKE_CXX_STANDARD=11 + -DPYTHON_EXECUTABLE=$(python3 -c "import sys; print(sys.executable)") + + - name: Build + run: cmake --build build -j 2 + + - name: Python tests + run: cmake --build build --target pytest + + - name: C++ tests + run: cmake --build build --target cpptest + + - name: Interface test + run: cmake --build build --target test_cmake_build + + + # This tests an "install" with the CMake tools + install-classic: + name: "🐍 3.7 • Debian • x86 • Install" + runs-on: ubuntu-latest + container: i386/debian:buster + + steps: + - uses: actions/checkout@v1 # v1 is required to run inside docker + + - name: Install requirements + run: | + apt-get update + apt-get install -y git make cmake g++ libeigen3-dev python3-dev python3-pip + pip3 install "pytest==6.*" + + - name: Configure for install + run: > + cmake . + -DPYBIND11_INSTALL=1 -DPYBIND11_TEST=0 + -DPYTHON_EXECUTABLE=$(python3 -c "import sys; print(sys.executable)") + + - name: Make and install + run: make install + + - name: Copy tests to new directory + run: cp -a tests /pybind11-tests + + - name: Make a new test directory + run: mkdir /build-tests + + - name: Configure tests + run: > + cmake ../pybind11-tests + -DDOWNLOAD_CATCH=ON + -DPYBIND11_WERROR=ON + -DPYTHON_EXECUTABLE=$(python3 -c "import sys; print(sys.executable)") + working-directory: /build-tests + + - name: Python tests + run: make pytest -j 2 + working-directory: /build-tests + + + # This verifies that the documentation is not horribly broken, and does a + # basic validation check on the SDist. + doxygen: + name: "Documentation build test" + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-python@v5 + with: + python-version: "3.x" + + - name: Install Doxygen + run: sudo apt-get install -y doxygen librsvg2-bin # Changed to rsvg-convert in 20.04 + + - name: Build docs + run: pipx run nox -s docs + + - name: Make SDist + run: pipx run nox -s build -- --sdist + + - run: git status --ignored + + - name: Check local include dir + run: > + ls pybind11; + python3 -c "import pybind11, pathlib; assert (a := pybind11.get_include()) == (b := str(pathlib.Path('include').resolve())), f'{a} != {b}'" + + - name: Compare Dists (headers only) + working-directory: include + run: | + python3 -m pip install --user -U ../dist/*.tar.gz + installed=$(python3 -c "import pybind11; print(pybind11.get_include() + '/pybind11')") + diff -rq $installed ./pybind11 + + win32: + strategy: + fail-fast: false + matrix: + python: + - 3.6 + - 3.7 + - 3.8 + - 3.9 + + include: + - python: 3.9 + 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 + + steps: + - uses: actions/checkout@v4 + + - name: Setup Python ${{ matrix.python }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python }} + architecture: x86 + + - name: Update CMake + uses: jwlawson/actions-setup-cmake@v2.0 + + - name: Prepare MSVC + uses: ilammy/msvc-dev-cmd@v1.13.0 + with: + arch: x86 + + - name: Prepare env + run: | + python -m pip install -r tests/requirements.txt + + # First build - C++11 mode and inplace + - name: Configure ${{ matrix.args }} + run: > + cmake -S . -B build + -G "Visual Studio 16 2019" -A Win32 + -DPYBIND11_WERROR=ON + -DDOWNLOAD_CATCH=ON + -DDOWNLOAD_EIGEN=ON + ${{ matrix.args }} + - name: Build C++11 + run: cmake --build build -j 2 + + - name: Python tests + run: cmake --build build -t pytest + + win32-debug: + strategy: + fail-fast: false + matrix: + python: + - 3.8 + - 3.9 + + include: + - python: 3.9 + args: -DCMAKE_CXX_STANDARD=20 + - python: 3.8 + args: -DCMAKE_CXX_STANDARD=17 + + name: "🐍 ${{ matrix.python }} • MSVC 2019 (Debug) • x86 ${{ matrix.args }}" + runs-on: windows-2019 + + steps: + - uses: actions/checkout@v4 + + - name: Setup Python ${{ matrix.python }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python }} + architecture: x86 + + - name: Update CMake + uses: jwlawson/actions-setup-cmake@v2.0 + + - name: Prepare MSVC + uses: ilammy/msvc-dev-cmd@v1.13.0 + with: + arch: x86 + + - name: Prepare env + run: | + python -m pip install -r tests/requirements.txt + + # First build - C++11 mode and inplace + - name: Configure ${{ matrix.args }} + run: > + cmake -S . -B build + -G "Visual Studio 16 2019" -A Win32 + -DCMAKE_BUILD_TYPE=Debug + -DPYBIND11_WERROR=ON + -DDOWNLOAD_CATCH=ON + -DDOWNLOAD_EIGEN=ON + ${{ matrix.args }} + - name: Build C++11 + run: cmake --build build --config Debug -j 2 + + - name: Python tests + run: cmake --build build --config Debug -t pytest + + + windows-2022: + strategy: + fail-fast: false + matrix: + python: + - 3.9 + + name: "🐍 ${{ matrix.python }} • MSVC 2022 C++20 • x64" + runs-on: windows-2022 + + steps: + - uses: actions/checkout@v4 + + - name: Setup Python ${{ matrix.python }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python }} + + - name: Prepare env + # Ensure use of NumPy 2 (via NumPy nightlies but can be changed soon) + run: | + python3 -m pip install -r tests/requirements.txt + python3 -m pip install 'numpy>=2.0.0b1' 'scipy>=1.13.0rc1' + + - name: Update CMake + uses: jwlawson/actions-setup-cmake@v2.0 + + - name: Configure C++20 + run: > + cmake -S . -B build + -DPYBIND11_WERROR=ON + -DDOWNLOAD_CATCH=ON + -DDOWNLOAD_EIGEN=ON + -DCMAKE_CXX_STANDARD=20 + + - name: Build C++20 + run: cmake --build build -j 2 + + - name: Python tests + run: cmake --build build --target pytest + + - name: C++20 tests + run: cmake --build build --target cpptest -j 2 + + - 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 + defaults: + run: + shell: msys2 {0} + strategy: + fail-fast: false + matrix: + include: + - { sys: mingw64, env: x86_64 } + - { sys: mingw32, env: i686 } + steps: + - uses: msys2/setup-msys2@v2 + with: + msystem: ${{matrix.sys}} + install: >- + git + mingw-w64-${{matrix.env}}-gcc + mingw-w64-${{matrix.env}}-python-pip + mingw-w64-${{matrix.env}}-python-numpy + mingw-w64-${{matrix.env}}-cmake + mingw-w64-${{matrix.env}}-make + mingw-w64-${{matrix.env}}-python-pytest + mingw-w64-${{matrix.env}}-eigen3 + mingw-w64-${{matrix.env}}-boost + mingw-w64-${{matrix.env}}-catch + + - uses: msys2/setup-msys2@v2 + if: matrix.sys == 'mingw64' + with: + msystem: ${{matrix.sys}} + install: >- + git + mingw-w64-${{matrix.env}}-python-scipy + + - uses: actions/checkout@v4 + + - 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 -DPYBIND11_WERROR=ON -DDOWNLOAD_CATCH=ON + -DPYTHON_EXECUTABLE=$(python -c "import sys; print(sys.executable)") + -S . -B build + + - name: Build C++11 + run: cmake --build build -j 2 + + - name: Python tests C++11 + run: cmake --build build --target pytest -j 2 + + - name: C++11 tests + run: PYTHONHOME=/${{matrix.sys}} PYTHONPATH=/${{matrix.sys}} cmake --build build --target cpptest -j 2 + + - name: Interface test C++11 + run: PYTHONHOME=/${{matrix.sys}} PYTHONPATH=/${{matrix.sys}} cmake --build build --target test_cmake_build + + - name: Clean directory + run: git clean -fdx + + - name: Configure C++14 + run: >- + cmake -G "MinGW Makefiles" -DCMAKE_CXX_STANDARD=14 -DPYBIND11_WERROR=ON -DDOWNLOAD_CATCH=ON + -DPYTHON_EXECUTABLE=$(python -c "import sys; print(sys.executable)") + -S . -B build2 + + - name: Build C++14 + run: cmake --build build2 -j 2 + + - name: Python tests C++14 + run: cmake --build build2 --target pytest -j 2 + + - name: C++14 tests + run: PYTHONHOME=/${{matrix.sys}} PYTHONPATH=/${{matrix.sys}} cmake --build build2 --target cpptest -j 2 + + - name: Interface test C++14 + run: PYTHONHOME=/${{matrix.sys}} PYTHONPATH=/${{matrix.sys}} cmake --build build2 --target test_cmake_build + + - name: Clean directory + run: git clean -fdx + + - name: Configure C++17 + run: >- + cmake -G "MinGW Makefiles" -DCMAKE_CXX_STANDARD=17 -DPYBIND11_WERROR=ON -DDOWNLOAD_CATCH=ON + -DPYTHON_EXECUTABLE=$(python -c "import sys; print(sys.executable)") + -S . -B build3 + + - name: Build C++17 + run: cmake --build build3 -j 2 + + - name: Python tests C++17 + run: cmake --build build3 --target pytest -j 2 + + - name: C++17 tests + run: PYTHONHOME=/${{matrix.sys}} PYTHONPATH=/${{matrix.sys}} cmake --build build3 --target cpptest -j 2 + + - 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@v4 + + - name: Set up Clang + uses: egor-tensin/setup-clang@v1 + + - name: Setup Python ${{ matrix.python }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python }} + + - name: Update CMake + uses: jwlawson/actions-setup-cmake@v2.0 + + - name: Install ninja-build tool + uses: seanmiddleditch/gha-setup-ninja@v4 + + - 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@v4 + + - 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@v2.0 + + - 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/extern/pybind11/.github/workflows/configure.yml b/extern/pybind11/.github/workflows/configure.yml new file mode 100644 index 000000000..dca37864c --- /dev/null +++ b/extern/pybind11/.github/workflows/configure.yml @@ -0,0 +1,92 @@ +name: Config + +on: + workflow_dispatch: + pull_request: + push: + branches: + - master + - 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. + cmake: + strategy: + fail-fast: false + matrix: + runs-on: [ubuntu-20.04, macos-latest, windows-latest] + arch: [x64] + cmake: ["3.26"] + + include: + - runs-on: ubuntu-20.04 + arch: x64 + cmake: "3.5" + + - runs-on: ubuntu-20.04 + arch: x64 + cmake: "3.27" + + - runs-on: macos-latest + arch: x64 + cmake: "3.7" + + - runs-on: windows-2019 + arch: x64 # x86 compilers seem to be missing on 2019 image + cmake: "3.18" + + name: 🐍 3.7 • CMake ${{ matrix.cmake }} • ${{ matrix.runs-on }} + runs-on: ${{ matrix.runs-on }} + + steps: + - uses: actions/checkout@v4 + + - name: Setup Python 3.7 + uses: actions/setup-python@v5 + with: + python-version: 3.7 + architecture: ${{ matrix.arch }} + + - name: Prepare env + run: python -m pip install -r tests/requirements.txt + + # 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@v2.0 + with: + cmake-version: ${{ matrix.cmake }} + + # These steps use a directory with a space in it intentionally + - name: Make build directories + run: mkdir "build dir" + + - name: Configure + working-directory: build dir + shell: bash + run: > + cmake .. + -DPYBIND11_WERROR=ON + -DDOWNLOAD_CATCH=ON + -DPYTHON_EXECUTABLE=$(python -c "import sys; print(sys.executable)") + + # Only build and test if this was manually triggered in the GitHub UI + - name: Build + working-directory: build dir + if: github.event_name == 'workflow_dispatch' + run: cmake --build . --config Release + + - name: Test + working-directory: build dir + if: github.event_name == 'workflow_dispatch' + run: cmake --build . --config Release --target check diff --git a/extern/pybind11/.github/workflows/format.yml b/extern/pybind11/.github/workflows/format.yml new file mode 100644 index 000000000..1eaa56e1c --- /dev/null +++ b/extern/pybind11/.github/workflows/format.yml @@ -0,0 +1,60 @@ +# This is a format job. Pre-commit has a first-party GitHub action, so we use +# that: https://github.com/pre-commit/action + +name: Format + +on: + workflow_dispatch: + pull_request: + push: + branches: + - master + - stable + - "v*" + +permissions: + contents: read + +env: + FORCE_COLOR: 3 + # For cmake: + VERBOSE: 1 + +jobs: + pre-commit: + name: Format + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: "3.x" + - name: Add matchers + run: echo "::add-matcher::$GITHUB_WORKSPACE/.github/matchers/pylint.json" + - uses: pre-commit/action@v3.0.1 + with: + # Slow hooks are marked with manual - slow is okay here, run them too + extra_args: --hook-stage manual --all-files + + clang-tidy: + # When making changes here, please also review the "Clang-Tidy" section + # in .github/CONTRIBUTING.md and update as needed. + name: Clang-Tidy + runs-on: ubuntu-latest + container: silkeh/clang:15-bullseye + steps: + - uses: actions/checkout@v4 + + - name: Install requirements + run: apt-get update && apt-get install -y git python3-dev python3-pytest + + - name: Configure + run: > + cmake -S . -B build + -DCMAKE_CXX_CLANG_TIDY="$(which clang-tidy);--use-color;--warnings-as-errors=*" + -DDOWNLOAD_EIGEN=ON + -DDOWNLOAD_CATCH=ON + -DCMAKE_CXX_STANDARD=17 + + - name: Build + run: cmake --build build -j 2 -- --keep-going diff --git a/extern/pybind11/.github/workflows/labeler.yml b/extern/pybind11/.github/workflows/labeler.yml new file mode 100644 index 000000000..dd7105662 --- /dev/null +++ b/extern/pybind11/.github/workflows/labeler.yml @@ -0,0 +1,25 @@ +name: Labeler +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@v4 + 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/extern/pybind11/.github/workflows/pip.yml b/extern/pybind11/.github/workflows/pip.yml new file mode 100644 index 000000000..19baf57d9 --- /dev/null +++ b/extern/pybind11/.github/workflows/pip.yml @@ -0,0 +1,114 @@ +name: Pip + +on: + workflow_dispatch: + pull_request: + push: + branches: + - master + - stable + - v* + release: + types: + - published + +permissions: + contents: read + +env: + PIP_BREAK_SYSTEM_PACKAGES: 1 + PIP_ONLY_BINARY: numpy + +jobs: + # This builds the sdists and wheels and makes sure the files are exactly as + # expected. Using Windows and Python 3.6, since that is often the most + # challenging matrix element. + test-packaging: + name: 🐍 3.6 • 📦 tests • windows-latest + runs-on: windows-latest + + steps: + - uses: actions/checkout@v4 + + - name: Setup 🐍 3.6 + uses: actions/setup-python@v5 + with: + python-version: 3.6 + + - name: Prepare env + run: | + python -m pip install -r tests/requirements.txt + + - name: Python Packaging tests + run: pytest tests/extra_python_package/ + + + # This runs the packaging tests and also builds and saves the packages as + # artifacts. + packaging: + name: 🐍 3.8 • 📦 & 📦 tests • ubuntu-latest + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Setup 🐍 3.8 + uses: actions/setup-python@v5 + with: + python-version: 3.8 + + - name: Prepare env + run: | + python -m pip install -r tests/requirements.txt build twine + + - name: Python Packaging tests + run: pytest tests/extra_python_package/ + + - name: Build SDist and wheels + run: | + python -m build + PYBIND11_GLOBAL_SDIST=1 python -m build + + - name: Check metadata + run: twine check dist/* + + - name: Save standard package + uses: actions/upload-artifact@v4 + with: + name: standard + path: dist/pybind11-* + + - name: Save global package + uses: actions/upload-artifact@v4 + with: + name: global + path: dist/pybind11_global-* + + + + # When a GitHub release is made, upload the artifacts to PyPI + upload: + name: Upload to PyPI + runs-on: ubuntu-latest + if: github.event_name == 'release' && github.event.action == 'published' + needs: [packaging] + + steps: + - uses: actions/setup-python@v5 + with: + python-version: "3.x" + + # Downloads all to directories matching the artifact names + - uses: actions/download-artifact@v4 + + - name: Publish standard package + uses: pypa/gh-action-pypi-publish@release/v1 + with: + password: ${{ secrets.pypi_password }} + packages-dir: standard/ + + - name: Publish global package + uses: pypa/gh-action-pypi-publish@release/v1 + with: + password: ${{ secrets.pypi_password_global }} + packages-dir: global/ diff --git a/extern/pybind11/.github/workflows/upstream.yml b/extern/pybind11/.github/workflows/upstream.yml new file mode 100644 index 000000000..389260038 --- /dev/null +++ b/extern/pybind11/.github/workflows/upstream.yml @@ -0,0 +1,116 @@ +name: Upstream + +on: + workflow_dispatch: + pull_request: + +permissions: + contents: read + +concurrency: + group: upstream-${{ github.ref }} + cancel-in-progress: true + +env: + PIP_BREAK_SYSTEM_PACKAGES: 1 + # For cmake: + VERBOSE: 1 + +jobs: + standard: + name: "🐍 3.13 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@v4 + + - name: Setup Python 3.13 + uses: actions/setup-python@v5 + with: + python-version: "3.13" + allow-prereleases: true + + - name: Setup Boost + run: sudo apt-get install libboost-dev + + - name: Update CMake + uses: jwlawson/actions-setup-cmake@v2.0 + + - name: Run pip installs + run: | + python -m pip install --upgrade pip + python -m pip install -r tests/requirements.txt + + - 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 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 build11 -j 2 + + - name: Python tests C++11 + run: cmake --build build11 --target pytest -j 2 + + - name: C++11 tests + run: cmake --build build11 --target cpptest -j 2 + + - name: Interface test C++11 + 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 build17 + -DPYBIND11_WERROR=ON + -DDOWNLOAD_CATCH=ON + -DDOWNLOAD_EIGEN=ON + -DCMAKE_CXX_STANDARD=17 + + - name: Build C++17 + run: cmake --build build17 -j 2 + + - name: Python tests C++17 + run: cmake --build build17 --target pytest + + - 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 build17max + -DPYBIND11_WERROR=ON + -DDOWNLOAD_CATCH=ON + -DDOWNLOAD_EIGEN=ON + -DCMAKE_CXX_STANDARD=17 + -DPYBIND11_INTERNALS_VERSION=10000000 + + - name: Build (unstable ABI) + run: cmake --build build17max -j 2 + + - name: Python tests (unstable ABI) + run: cmake --build build17max --target pytest + + - 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: | + pip install setuptools + pytest tests/extra_setuptools diff --git a/extern/pybind11/.gitignore b/extern/pybind11/.gitignore new file mode 100644 index 000000000..43d5094c9 --- /dev/null +++ b/extern/pybind11/.gitignore @@ -0,0 +1,46 @@ +CMakeCache.txt +CMakeFiles +Makefile +cmake_install.cmake +cmake_uninstall.cmake +.DS_Store +*.so +*.pyd +*.dll +*.sln +*.sdf +*.opensdf +*.vcxproj +*.vcxproj.user +*.filters +example.dir +Win32 +x64 +Release +Debug +.vs +CTestTestfile.cmake +Testing +autogen +MANIFEST +/.ninja_* +/*.ninja +/docs/.build +*.py[co] +*.egg-info +*~ +.*.swp +.DS_Store +/dist +/*build* +.cache/ +sosize-*.txt +pybind11Config*.cmake +pybind11Targets.cmake +/*env* +/.vscode +/pybind11/include/* +/pybind11/share/* +/docs/_build/* +.ipynb_checkpoints/ +tests/main.cpp diff --git a/extern/pybind11/.pre-commit-config.yaml b/extern/pybind11/.pre-commit-config.yaml new file mode 100644 index 000000000..2bb47b21c --- /dev/null +++ b/extern/pybind11/.pre-commit-config.yaml @@ -0,0 +1,155 @@ +# To use: +# +# pre-commit run -a +# +# Or: +# +# pre-commit install # (runs every time you commit in git) +# +# To update this file: +# +# pre-commit autoupdate +# +# 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: "v17.0.6" + hooks: + - id: clang-format + types_or: [c++, c, cuda] + +# Ruff, the Python auto-correcting linter/formatter written in Rust +- repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.2.0 + hooks: + - id: ruff + args: ["--fix", "--show-fixes"] + - id: ruff-format + +# Check static types with mypy +- repo: https://github.com/pre-commit/mirrors-mypy + rev: "v1.8.0" + 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.5.0" + hooks: + - id: check-added-large-files + - id: check-case-conflict + - id: check-docstring-first + - id: check-merge-conflict + - id: check-symlinks + - id: check-toml + - id: check-yaml + - id: debug-statements + - id: end-of-file-fixer + - id: mixed-line-ending + - id: requirements-txt-fixer + - id: trailing-whitespace + +# Also code format the docs +- repo: https://github.com/asottile/blacken-docs + rev: "1.16.0" + hooks: + - id: blacken-docs + additional_dependencies: + - black==23.* + +# Changes tabs to spaces +- repo: https://github.com/Lucas-C/pre-commit-hooks + rev: "v1.5.4" + hooks: + - id: remove-tabs + +# Avoid directional quotes +- repo: https://github.com/sirosen/texthooks + rev: "0.6.4" + hooks: + - id: fix-ligatures + - id: fix-smartquotes + +# Checking for common mistakes +- repo: https://github.com/pre-commit/pygrep-hooks + rev: "v1.10.0" + hooks: + - id: rst-backticks + - id: rst-directive-colons + - id: rst-inline-touching-normal + +# Checks the manifest for missing files (native support) +- repo: https://github.com/mgedmin/check-manifest + rev: "0.49" + hooks: + - id: check-manifest + # This is a slow hook, so only run this if --hook-stage manual is passed + stages: [manual] + 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.2.6" + hooks: + - id: codespell + exclude: ".supp$" + args: ["-x.codespell-ignore-lines", "-Lccompiler"] + +# Check for common shell mistakes +- repo: https://github.com/shellcheck-py/shellcheck-py + rev: "v0.9.0.6" + hooks: + - id: shellcheck + +# Disallow some common capitalization mistakes +- repo: local + hooks: + - id: disallow-caps + name: Disallow improper capitalization + language: pygrep + entry: PyBind|\bNumpy\b|Cmake|CCache|PyTest + exclude: ^\.pre-commit-config.yaml$ + +# PyLint has native support - not always usable, but works for us +- repo: https://github.com/PyCQA/pylint + rev: "v3.0.3" + hooks: + - id: pylint + files: ^pybind11 + +- repo: https://github.com/python-jsonschema/check-jsonschema + rev: 0.28.0 + hooks: + - id: check-readthedocs + - id: check-github-workflows + - id: check-dependabot diff --git a/extern/pybind11/.readthedocs.yml b/extern/pybind11/.readthedocs.yml new file mode 100644 index 000000000..a2b802f73 --- /dev/null +++ b/extern/pybind11/.readthedocs.yml @@ -0,0 +1,20 @@ +# https://blog.readthedocs.com/migrate-configuration-v2/ + +version: 2 + +build: + os: ubuntu-22.04 + apt_packages: + - librsvg2-bin + tools: + python: "3.11" + +sphinx: + configuration: docs/conf.py + +python: + install: + - requirements: docs/requirements.txt + +formats: + - pdf diff --git a/extern/pybind11/CMakeLists.txt b/extern/pybind11/CMakeLists.txt new file mode 100644 index 000000000..7db1bf668 --- /dev/null +++ b/extern/pybind11/CMakeLists.txt @@ -0,0 +1,373 @@ +# CMakeLists.txt -- Build system for the pybind11 modules +# +# Copyright (c) 2015 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. + +# Propagate this policy (FindPythonInterp removal) so it can be detected later +if(NOT CMAKE_VERSION VERSION_LESS "3.27") + cmake_policy(GET CMP0148 _pybind11_cmp0148) +endif() + +cmake_minimum_required(VERSION 3.5) + +# The `cmake_minimum_required(VERSION 3.5...3.27)` 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.27) + cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}) +else() + cmake_policy(VERSION 3.27) +endif() + +if(_pybind11_cmp0148) + cmake_policy(SET CMP0148 ${_pybind11_cmp0148}) + unset(_pybind11_cmp0148) +endif() + +# Avoid infinite recursion if tests include this as a subdirectory +if(DEFINED PYBIND11_MASTER_PROJECT) + return() +endif() + +# Extract project version from source +file(STRINGS "${CMAKE_CURRENT_SOURCE_DIR}/include/pybind11/detail/common.h" + pybind11_version_defines REGEX "#define PYBIND11_VERSION_(MAJOR|MINOR|PATCH) ") + +foreach(ver ${pybind11_version_defines}) + if(ver MATCHES [[#define PYBIND11_VERSION_(MAJOR|MINOR|PATCH) +([^ ]+)$]]) + set(PYBIND11_VERSION_${CMAKE_MATCH_1} "${CMAKE_MATCH_2}") + endif() +endforeach() + +if(PYBIND11_VERSION_PATCH MATCHES [[\.([a-zA-Z0-9]+)$]]) + set(pybind11_VERSION_TYPE "${CMAKE_MATCH_1}") +endif() +string(REGEX MATCH "^[0-9]+" PYBIND11_VERSION_PATCH "${PYBIND11_VERSION_PATCH}") + +project( + pybind11 + LANGUAGES CXX + VERSION "${PYBIND11_VERSION_MAJOR}.${PYBIND11_VERSION_MINOR}.${PYBIND11_VERSION_PATCH}") + +# Standard includes +include(GNUInstallDirs) +include(CMakePackageConfigHelpers) +include(CMakeDependentOption) + +if(NOT pybind11_FIND_QUIETLY) + message(STATUS "pybind11 v${pybind11_VERSION} ${pybind11_VERSION_TYPE}") +endif() + +# Check if pybind11 is being used directly or via add_subdirectory +if(CMAKE_SOURCE_DIR STREQUAL PROJECT_SOURCE_DIR) + ### Warn if not an out-of-source builds + if(CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_CURRENT_BINARY_DIR) + set(lines + "You are building in-place. If that is not what you intended to " + "do, you can clean the source directory with:\n" + "rm -r CMakeCache.txt CMakeFiles/ cmake_uninstall.cmake pybind11Config.cmake " + "pybind11ConfigVersion.cmake tests/CMakeFiles/\n") + message(AUTHOR_WARNING ${lines}) + endif() + + set(PYBIND11_MASTER_PROJECT ON) + + if(OSX AND CMAKE_VERSION VERSION_LESS 3.7) + # Bug in macOS CMake < 3.7 is unable to download catch + message(WARNING "CMAKE 3.7+ needed on macOS to download catch, and newer HIGHLY recommended") + elseif(WINDOWS AND CMAKE_VERSION VERSION_LESS 3.8) + # Only tested with 3.8+ in CI. + message(WARNING "CMAKE 3.8+ tested on Windows, previous versions untested") + endif() + + message(STATUS "CMake ${CMAKE_VERSION}") + + if(CMAKE_CXX_STANDARD) + set(CMAKE_CXX_EXTENSIONS OFF) + set(CMAKE_CXX_STANDARD_REQUIRED ON) + endif() + + set(pybind11_system "") + + set_property(GLOBAL PROPERTY USE_FOLDERS ON) + if(CMAKE_VERSION VERSION_LESS "3.18") + set(_pybind11_findpython_default OFF) + else() + set(_pybind11_findpython_default ON) + endif() +else() + set(PYBIND11_MASTER_PROJECT OFF) + set(pybind11_system SYSTEM) + set(_pybind11_findpython_default OFF) +endif() + +# Options +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_DISABLE_HANDLE_TYPE_NAME_DEFAULT_IMPLEMENTATION + "To enforce that a handle_type_name<> specialization exists" OFF) +option(PYBIND11_SIMPLE_GIL_MANAGEMENT + "Use simpler GIL management logic that does not support disassociation" OFF) +option(PYBIND11_NUMPY_1_ONLY + "Disable NumPy 2 support to avoid changes to previous pybind11 versions." OFF) +set(PYBIND11_INTERNALS_VERSION + "" + CACHE STRING "Override the ABI version, may be used to enable the unstable ABI.") + +if(PYBIND11_DISABLE_HANDLE_TYPE_NAME_DEFAULT_IMPLEMENTATION) + add_compile_definitions(PYBIND11_DISABLE_HANDLE_TYPE_NAME_DEFAULT_IMPLEMENTATION) +endif() +if(PYBIND11_SIMPLE_GIL_MANAGEMENT) + add_compile_definitions(PYBIND11_SIMPLE_GIL_MANAGEMENT) +endif() +if(PYBIND11_NUMPY_1_ONLY) + add_compile_definitions(PYBIND11_NUMPY_1_ONLY) +endif() + +cmake_dependent_option( + USE_PYTHON_INCLUDE_DIR + "Install pybind11 headers in Python include directory instead of default installation prefix" + OFF "PYBIND11_INSTALL" OFF) + +cmake_dependent_option(PYBIND11_FINDPYTHON "Force new FindPython" ${_pybind11_findpython_default} + "NOT CMAKE_VERSION VERSION_LESS 3.12" OFF) + +# Allow PYTHON_EXECUTABLE if in FINDPYTHON mode and building pybind11's tests +# (makes transition easier while we support both modes). +if(PYBIND11_MASTER_PROJECT + AND PYBIND11_FINDPYTHON + AND DEFINED PYTHON_EXECUTABLE + AND NOT DEFINED Python_EXECUTABLE) + set(Python_EXECUTABLE "${PYTHON_EXECUTABLE}") +endif() + +# NB: when adding a header don't forget to also add it to setup.py +set(PYBIND11_HEADERS + include/pybind11/detail/class.h + include/pybind11/detail/common.h + include/pybind11/detail/descr.h + include/pybind11/detail/init.h + include/pybind11/detail/internals.h + include/pybind11/detail/type_caster_base.h + include/pybind11/detail/typeid.h + include/pybind11/attr.h + include/pybind11/buffer_info.h + include/pybind11/cast.h + include/pybind11/chrono.h + include/pybind11/common.h + 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 + include/pybind11/gil_safe_call_once.h + include/pybind11/iostream.h + include/pybind11/functional.h + include/pybind11/numpy.h + include/pybind11/operators.h + include/pybind11/pybind11.h + include/pybind11/pytypes.h + include/pybind11/stl.h + include/pybind11/stl_bind.h + include/pybind11/stl/filesystem.h + include/pybind11/type_caster_pyobject_ptr.h + include/pybind11/typing.h) + +# Compare with grep and warn if mismatched +if(PYBIND11_MASTER_PROJECT AND NOT CMAKE_VERSION VERSION_LESS 3.12) + file( + GLOB_RECURSE _pybind11_header_check + LIST_DIRECTORIES false + RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" + CONFIGURE_DEPENDS "include/pybind11/*.h") + set(_pybind11_here_only ${PYBIND11_HEADERS}) + set(_pybind11_disk_only ${_pybind11_header_check}) + list(REMOVE_ITEM _pybind11_here_only ${_pybind11_header_check}) + list(REMOVE_ITEM _pybind11_disk_only ${PYBIND11_HEADERS}) + if(_pybind11_here_only) + message(AUTHOR_WARNING "PYBIND11_HEADERS has extra files:" ${_pybind11_here_only}) + endif() + if(_pybind11_disk_only) + message(AUTHOR_WARNING "PYBIND11_HEADERS is missing files:" ${_pybind11_disk_only}) + endif() +endif() + +# CMake 3.12 added list(TRANSFORM PREPEND +# But we can't use it yet +string(REPLACE "include/" "${CMAKE_CURRENT_SOURCE_DIR}/include/" PYBIND11_HEADERS + "${PYBIND11_HEADERS}") + +# Cache variable so this can be used in parent projects +set(pybind11_INCLUDE_DIR + "${CMAKE_CURRENT_LIST_DIR}/include" + CACHE INTERNAL "Directory where pybind11 headers are located") + +# Backward compatible variable for add_subdirectory mode +if(NOT PYBIND11_MASTER_PROJECT) + set(PYBIND11_INCLUDE_DIR + "${pybind11_INCLUDE_DIR}" + CACHE INTERNAL "") +endif() + +# Note: when creating targets, you cannot use if statements at configure time - +# you need generator expressions, because those will be placed in the target file. +# You can also place ifs *in* the Config.in, but not here. + +# This section builds targets, but does *not* touch Python +# Non-IMPORT targets cannot be defined twice +if(NOT TARGET pybind11_headers) + # Build the headers-only target (no Python included): + # (long name used here to keep this from clashing in subdirectory mode) + add_library(pybind11_headers INTERFACE) + add_library(pybind11::pybind11_headers ALIAS pybind11_headers) # to match exported target + add_library(pybind11::headers ALIAS pybind11_headers) # easier to use/remember + + target_include_directories( + pybind11_headers ${pybind11_system} INTERFACE $ + $) + + target_compile_features(pybind11_headers INTERFACE cxx_inheriting_constructors cxx_user_literals + cxx_right_angle_brackets) + if(NOT "${PYBIND11_INTERNALS_VERSION}" STREQUAL "") + target_compile_definitions( + pybind11_headers INTERFACE "PYBIND11_INTERNALS_VERSION=${PYBIND11_INTERNALS_VERSION}") + endif() +else() + # It is invalid to install a target twice, too. + set(PYBIND11_INSTALL OFF) +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) + file(RELATIVE_PATH CMAKE_INSTALL_INCLUDEDIR ${CMAKE_INSTALL_PREFIX} ${Python_INCLUDE_DIRS}) +elseif(USE_PYTHON_INCLUDE_DIR AND DEFINED PYTHON_INCLUDE_DIR) + file(RELATIVE_PATH CMAKE_INSTALL_INCLUDEDIR ${CMAKE_INSTALL_PREFIX} ${PYTHON_INCLUDE_DIRS}) +endif() + +if(PYBIND11_INSTALL) + install(DIRECTORY ${pybind11_INCLUDE_DIR}/pybind11 DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) + set(PYBIND11_CMAKECONFIG_INSTALL_DIR + "${CMAKE_INSTALL_DATAROOTDIR}/cmake/${PROJECT_NAME}" + CACHE STRING "install path for pybind11Config.cmake") + + if(IS_ABSOLUTE "${CMAKE_INSTALL_INCLUDEDIR}") + set(pybind11_INCLUDEDIR "${CMAKE_INSTALL_FULL_INCLUDEDIR}") + else() + set(pybind11_INCLUDEDIR "\$\{PACKAGE_PREFIX_DIR\}/${CMAKE_INSTALL_INCLUDEDIR}") + endif() + + configure_package_config_file( + tools/${PROJECT_NAME}Config.cmake.in "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Config.cmake" + INSTALL_DESTINATION ${PYBIND11_CMAKECONFIG_INSTALL_DIR}) + + if(CMAKE_VERSION VERSION_LESS 3.14) + # Remove CMAKE_SIZEOF_VOID_P from ConfigVersion.cmake since the library does + # not depend on architecture specific settings or libraries. + set(_PYBIND11_CMAKE_SIZEOF_VOID_P ${CMAKE_SIZEOF_VOID_P}) + unset(CMAKE_SIZEOF_VOID_P) + + write_basic_package_version_file( + ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake + VERSION ${PROJECT_VERSION} + COMPATIBILITY AnyNewerVersion) + + set(CMAKE_SIZEOF_VOID_P ${_PYBIND11_CMAKE_SIZEOF_VOID_P}) + else() + # CMake 3.14+ natively supports header-only libraries + write_basic_package_version_file( + ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake + VERSION ${PROJECT_VERSION} + COMPATIBILITY AnyNewerVersion ARCH_INDEPENDENT) + endif() + + install( + FILES ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Config.cmake + ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake + tools/FindPythonLibsNew.cmake + tools/pybind11Common.cmake + tools/pybind11Tools.cmake + tools/pybind11NewTools.cmake + DESTINATION ${PYBIND11_CMAKECONFIG_INSTALL_DIR}) + + if(NOT PYBIND11_EXPORT_NAME) + set(PYBIND11_EXPORT_NAME "${PROJECT_NAME}Targets") + endif() + + install(TARGETS pybind11_headers EXPORT "${PYBIND11_EXPORT_NAME}") + + install( + EXPORT "${PYBIND11_EXPORT_NAME}" + NAMESPACE "pybind11::" + DESTINATION ${PYBIND11_CMAKECONFIG_INSTALL_DIR}) + + # pkg-config support + if(NOT prefix_for_pc_file) + if(IS_ABSOLUTE "${CMAKE_INSTALL_DATAROOTDIR}") + set(prefix_for_pc_file "${CMAKE_INSTALL_PREFIX}") + else() + set(pc_datarootdir "${CMAKE_INSTALL_DATAROOTDIR}") + if(CMAKE_VERSION VERSION_LESS 3.20) + set(prefix_for_pc_file "\${pcfiledir}/..") + while(pc_datarootdir) + get_filename_component(pc_datarootdir "${pc_datarootdir}" DIRECTORY) + string(APPEND prefix_for_pc_file "/..") + endwhile() + else() + cmake_path(RELATIVE_PATH CMAKE_INSTALL_PREFIX BASE_DIRECTORY CMAKE_INSTALL_DATAROOTDIR + OUTPUT_VARIABLE prefix_for_pc_file) + endif() + endif() + 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" + "${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake" IMMEDIATE @ONLY) + + add_custom_target(uninstall COMMAND ${CMAKE_COMMAND} -P + ${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake) + endif() +endif() + +# BUILD_TESTING takes priority, but only if this is the master project +if(PYBIND11_MASTER_PROJECT AND DEFINED BUILD_TESTING) + if(BUILD_TESTING) + if(_pybind11_nopython) + message(FATAL_ERROR "Cannot activate tests in NOPYTHON mode") + else() + add_subdirectory(tests) + endif() + endif() +else() + if(PYBIND11_TEST) + if(_pybind11_nopython) + message(FATAL_ERROR "Cannot activate tests in NOPYTHON mode") + else() + add_subdirectory(tests) + endif() + endif() +endif() + +# Better symmetry with find_package(pybind11 CONFIG) mode. +if(NOT PYBIND11_MASTER_PROJECT) + set(pybind11_FOUND + TRUE + CACHE INTERNAL "True if pybind11 and all required components found on the system") +endif() diff --git a/extern/pybind11/LICENSE b/extern/pybind11/LICENSE new file mode 100644 index 000000000..e466b0dfd --- /dev/null +++ b/extern/pybind11/LICENSE @@ -0,0 +1,29 @@ +Copyright (c) 2016 Wenzel Jakob , All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +Please also refer to the file .github/CONTRIBUTING.md, which clarifies licensing of +external contributions to this project including patches, pull requests, etc. diff --git a/extern/pybind11/MANIFEST.in b/extern/pybind11/MANIFEST.in new file mode 100644 index 000000000..7ce83c552 --- /dev/null +++ b/extern/pybind11/MANIFEST.in @@ -0,0 +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 SECURITY.md pyproject.toml setup.py setup.cfg diff --git a/extern/pybind11/README.rst b/extern/pybind11/README.rst new file mode 100644 index 000000000..4032f97a5 --- /dev/null +++ b/extern/pybind11/README.rst @@ -0,0 +1,181 @@ +.. figure:: https://github.com/pybind/pybind11/raw/master/docs/pybind11-logo.png + :alt: pybind11 logo + +**pybind11 — Seamless operability between C++11 and Python** + +|Latest Documentation Status| |Stable Documentation Status| |Gitter chat| |GitHub Discussions| |CI| |Build status| + +|Repology| |PyPI package| |Conda-forge| |Python Versions| + +`Setuptools example `_ +• `Scikit-build example `_ +• `CMake example `_ + +.. start + + +**pybind11** is a lightweight header-only library that exposes C++ types +in Python and vice versa, mainly to create Python bindings of existing +C++ code. Its goals and syntax are similar to the excellent +`Boost.Python `_ +library by David Abrahams: to minimize boilerplate code in traditional +extension modules by inferring type information using compile-time +introspection. + +The main issue with Boost.Python—and the reason for creating such a +similar project—is Boost. Boost is an enormously large and complex suite +of utility libraries that works with almost every C++ compiler in +existence. This compatibility has its cost: arcane template tricks and +workarounds are necessary to support the oldest and buggiest of compiler +specimens. Now that C++11-compatible compilers are widely available, +this heavy machinery has become an excessively large and unnecessary +dependency. + +Think of this library as a tiny self-contained version of Boost.Python +with everything stripped away that isn't relevant for binding +generation. Without comments, the core header files only require ~4K +lines of code and depend on Python (3.6+, or PyPy) and the C++ +standard library. This compact implementation was possible thanks to +some C++11 language features (specifically: tuples, lambda functions and +variadic templates). Since its creation, this library has grown beyond +Boost.Python in many ways, leading to dramatically simpler binding code in many +common situations. + +Tutorial and reference documentation is provided at +`pybind11.readthedocs.io `_. +A PDF version of the manual is available +`here `_. +And the source code is always available at +`github.com/pybind/pybind11 `_. + + +Core features +------------- + + +pybind11 can map the following core C++ features to Python: + +- Functions accepting and returning custom data structures per value, + reference, or pointer +- Instance methods and static methods +- Overloaded functions +- Instance attributes and static attributes +- Arbitrary exception types +- Enumerations +- Callbacks +- Iterators and ranges +- Custom operators +- Single and multiple inheritance +- STL data structures +- Smart pointers with reference counting like ``std::shared_ptr`` +- Internal references with correct reference counting +- C++ classes with virtual (and pure virtual) methods can be extended + in Python +- Integrated NumPy support (NumPy 2 requires pybind11 2.12+) + +Goodies +------- + +In addition to the core functionality, pybind11 provides some extra +goodies: + +- Python 3.6+, and PyPy3 7.3 are supported with an implementation-agnostic + interface (pybind11 2.9 was the last version to support Python 2 and 3.5). + +- It is possible to bind C++11 lambda functions with captured + variables. The lambda capture data is stored inside the resulting + Python function object. + +- pybind11 uses C++11 move constructors and move assignment operators + whenever possible to efficiently transfer custom data types. + +- It's easy to expose the internal storage of custom data types through + Pythons' buffer protocols. This is handy e.g. for fast conversion + between C++ matrix classes like Eigen and NumPy without expensive + copy operations. + +- pybind11 can automatically vectorize functions so that they are + transparently applied to all entries of one or more NumPy array + arguments. + +- Python's slice-based access and assignment operations can be + supported with just a few lines of code. + +- Everything is contained in just a few header files; there is no need + to link against any additional libraries. + +- Binaries are generally smaller by a factor of at least 2 compared to + equivalent bindings generated by Boost.Python. A recent pybind11 + conversion of PyRosetta, an enormous Boost.Python binding project, + `reported `_ + a binary size reduction of **5.4x** and compile time reduction by + **5.8x**. + +- Function signatures are precomputed at compile time (using + ``constexpr``), leading to smaller binaries. + +- With little extra effort, C++ types can be pickled and unpickled + similar to regular Python objects. + +Supported compilers +------------------- + +1. Clang/LLVM 3.3 or newer (for Apple Xcode's clang, this is 5.0.0 or + newer) +2. GCC 4.8 or newer +3. Microsoft Visual Studio 2017 or newer +4. Intel classic C++ compiler 18 or newer (ICC 20.2 tested in CI) +5. Cygwin/GCC (previously tested on 2.5.1) +6. NVCC (CUDA 11.0 tested in CI) +7. NVIDIA PGI (20.9 tested in CI) + +About +----- + +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, +Dean Moldovan, Ben Pritchard, Jason Rhinelander, Boris Schäling, Pim +Schellart, Henry Schreiner, Ivan Smirnov, Boris Staletic, and Patrick Stewart. + +We thank Google for a generous financial contribution to the continuous +integration infrastructure used by this project. + + +Contributing +~~~~~~~~~~~~ + +See the `contributing +guide `_ +for information on building and contributing to pybind11. + +License +~~~~~~~ + +pybind11 is provided under a BSD-style license that can be found in the +`LICENSE `_ +file. By using, distributing, or contributing to this project, you agree +to the terms and conditions of this license. + +.. |Latest Documentation Status| image:: https://readthedocs.org/projects/pybind11/badge?version=latest + :target: http://pybind11.readthedocs.org/en/latest +.. |Stable Documentation Status| image:: https://img.shields.io/badge/docs-stable-blue.svg + :target: http://pybind11.readthedocs.org/en/stable +.. |Gitter chat| image:: https://img.shields.io/gitter/room/gitterHQ/gitter.svg + :target: https://gitter.im/pybind/Lobby +.. |CI| image:: https://github.com/pybind/pybind11/workflows/CI/badge.svg + :target: https://github.com/pybind/pybind11/actions +.. |Build status| image:: https://ci.appveyor.com/api/projects/status/riaj54pn4h08xy40?svg=true + :target: https://ci.appveyor.com/project/wjakob/pybind11 +.. |PyPI package| image:: https://img.shields.io/pypi/v/pybind11.svg + :target: https://pypi.org/project/pybind11/ +.. |Conda-forge| image:: https://img.shields.io/conda/vn/conda-forge/pybind11.svg + :target: https://github.com/conda-forge/pybind11-feedstock +.. |Repology| image:: https://repology.org/badge/latest-versions/python:pybind11.svg + :target: https://repology.org/project/python:pybind11/versions +.. |Python Versions| image:: https://img.shields.io/pypi/pyversions/pybind11.svg + :target: https://pypi.org/project/pybind11/ +.. |GitHub Discussions| image:: https://img.shields.io/static/v1?label=Discussions&message=Ask&color=blue&logo=github + :target: https://github.com/pybind/pybind11/discussions diff --git a/extern/pybind11/SECURITY.md b/extern/pybind11/SECURITY.md new file mode 100644 index 000000000..3d74611f2 --- /dev/null +++ b/extern/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/extern/pybind11/docs/Doxyfile b/extern/pybind11/docs/Doxyfile new file mode 100644 index 000000000..09138db36 --- /dev/null +++ b/extern/pybind11/docs/Doxyfile @@ -0,0 +1,21 @@ +PROJECT_NAME = pybind11 +INPUT = ../include/pybind11/ +RECURSIVE = YES + +GENERATE_HTML = NO +GENERATE_LATEX = NO +GENERATE_XML = YES +XML_OUTPUT = .build/doxygenxml +XML_PROGRAMLISTING = YES + +MACRO_EXPANSION = YES +EXPAND_ONLY_PREDEF = YES +EXPAND_AS_DEFINED = PYBIND11_RUNTIME_EXCEPTION + +ALIASES = "rst=\verbatim embed:rst" +ALIASES += "endrst=\endverbatim" + +QUIET = YES +WARNINGS = YES +WARN_IF_UNDOCUMENTED = NO +PREDEFINED = PYBIND11_NOINLINE diff --git a/extern/pybind11/docs/_static/css/custom.css b/extern/pybind11/docs/_static/css/custom.css new file mode 100644 index 000000000..7a49a6ac4 --- /dev/null +++ b/extern/pybind11/docs/_static/css/custom.css @@ -0,0 +1,3 @@ +.highlight .go { + color: #707070; +} diff --git a/extern/pybind11/docs/advanced/cast/chrono.rst b/extern/pybind11/docs/advanced/cast/chrono.rst new file mode 100644 index 000000000..fbd46057a --- /dev/null +++ b/extern/pybind11/docs/advanced/cast/chrono.rst @@ -0,0 +1,81 @@ +Chrono +====== + +When including the additional header file :file:`pybind11/chrono.h` conversions +from C++11 chrono datatypes to python datetime objects are automatically enabled. +This header also enables conversions of python floats (often from sources such +as ``time.monotonic()``, ``time.perf_counter()`` and ``time.process_time()``) +into durations. + +An overview of clocks in C++11 +------------------------------ + +A point of confusion when using these conversions is the differences between +clocks provided in C++11. There are three clock types defined by the C++11 +standard and users can define their own if needed. Each of these clocks have +different properties and when converting to and from python will give different +results. + +The first clock defined by the standard is ``std::chrono::system_clock``. This +clock measures the current date and time. However, this clock changes with to +updates to the operating system time. For example, if your time is synchronised +with a time server this clock will change. This makes this clock a poor choice +for timing purposes but good for measuring the wall time. + +The second clock defined in the standard is ``std::chrono::steady_clock``. +This clock ticks at a steady rate and is never adjusted. This makes it excellent +for timing purposes, however the value in this clock does not correspond to the +current date and time. Often this clock will be the amount of time your system +has been on, although it does not have to be. This clock will never be the same +clock as the system clock as the system clock can change but steady clocks +cannot. + +The third clock defined in the standard is ``std::chrono::high_resolution_clock``. +This clock is the clock that has the highest resolution out of the clocks in the +system. It is normally a typedef to either the system clock or the steady clock +but can be its own independent clock. This is important as when using these +conversions as the types you get in python for this clock might be different +depending on the system. +If it is a typedef of the system clock, python will get datetime objects, but if +it is a different clock they will be timedelta objects. + +Provided conversions +-------------------- + +.. rubric:: C++ to Python + +- ``std::chrono::system_clock::time_point`` → ``datetime.datetime`` + System clock times are converted to python datetime instances. They are + in the local timezone, but do not have any timezone information attached + to them (they are naive datetime objects). + +- ``std::chrono::duration`` → ``datetime.timedelta`` + Durations are converted to timedeltas, any precision in the duration + greater than microseconds is lost by rounding towards zero. + +- ``std::chrono::[other_clocks]::time_point`` → ``datetime.timedelta`` + Any clock time that is not the system clock is converted to a time delta. + This timedelta measures the time from the clocks epoch to now. + +.. rubric:: Python to C++ + +- ``datetime.datetime`` or ``datetime.date`` or ``datetime.time`` → ``std::chrono::system_clock::time_point`` + Date/time objects are converted into system clock timepoints. Any + timezone information is ignored and the type is treated as a naive + object. + +- ``datetime.timedelta`` → ``std::chrono::duration`` + Time delta are converted into durations with microsecond precision. + +- ``datetime.timedelta`` → ``std::chrono::[other_clocks]::time_point`` + Time deltas that are converted into clock timepoints are treated as + the amount of time from the start of the clocks epoch. + +- ``float`` → ``std::chrono::duration`` + Floats that are passed to C++ as durations be interpreted as a number of + seconds. These will be converted to the duration using ``duration_cast`` + from the float. + +- ``float`` → ``std::chrono::[other_clocks]::time_point`` + Floats that are passed to C++ as time points will be interpreted as the + number of seconds from the start of the clocks epoch. diff --git a/extern/pybind11/docs/advanced/cast/custom.rst b/extern/pybind11/docs/advanced/cast/custom.rst new file mode 100644 index 000000000..8138cac61 --- /dev/null +++ b/extern/pybind11/docs/advanced/cast/custom.rst @@ -0,0 +1,93 @@ +Custom type casters +=================== + +In very rare cases, applications may require custom type casters that cannot be +expressed using the abstractions provided by pybind11, thus requiring raw +Python C API calls. This is fairly advanced usage and should only be pursued by +experts who are familiar with the intricacies of Python reference counting. + +The following snippets demonstrate how this works for a very simple ``inty`` +type that that should be convertible from Python types that provide a +``__int__(self)`` method. + +.. code-block:: cpp + + struct inty { long long_value; }; + + void print(inty s) { + std::cout << s.long_value << std::endl; + } + +The following Python snippet demonstrates the intended usage from the Python side: + +.. code-block:: python + + class A: + def __int__(self): + return 123 + + + from example import print + + print(A()) + +To register the necessary conversion routines, it is necessary to add an +instantiation of the ``pybind11::detail::type_caster`` template. +Although this is an implementation detail, adding an instantiation of this +type is explicitly allowed. + +.. code-block:: cpp + + namespace PYBIND11_NAMESPACE { namespace detail { + template <> struct type_caster { + public: + /** + * This macro establishes the name 'inty' in + * function signatures and declares a local variable + * 'value' of type inty + */ + PYBIND11_TYPE_CASTER(inty, const_name("inty")); + + /** + * Conversion part 1 (Python->C++): convert a PyObject into a inty + * instance or return false upon failure. The second argument + * indicates whether implicit conversions should be applied. + */ + bool load(handle src, bool) { + /* Extract PyObject from handle */ + PyObject *source = src.ptr(); + /* Try converting into a Python integer value */ + PyObject *tmp = PyNumber_Long(source); + if (!tmp) + return false; + /* Now try to convert into a C++ int */ + value.long_value = PyLong_AsLong(tmp); + Py_DECREF(tmp); + /* Ensure return code was OK (to avoid out-of-range errors etc) */ + return !(value.long_value == -1 && !PyErr_Occurred()); + } + + /** + * Conversion part 2 (C++ -> Python): convert an inty instance into + * a Python object. The second and third arguments are used to + * indicate the return value policy and parent object (for + * ``return_value_policy::reference_internal``) and are generally + * ignored by implicit casters. + */ + static handle cast(inty src, return_value_policy /* policy */, handle /* parent */) { + return PyLong_FromLong(src.long_value); + } + }; + }} // namespace PYBIND11_NAMESPACE::detail + +.. note:: + + A ``type_caster`` defined with ``PYBIND11_TYPE_CASTER(T, ...)`` requires + that ``T`` is default-constructible (``value`` is first default constructed + and then ``load()`` assigns to it). + +.. warning:: + + When using custom type casters, it's important to declare them consistently + in every compilation unit of the Python extension module. Otherwise, + undefined behavior can ensue. diff --git a/extern/pybind11/docs/advanced/cast/eigen.rst b/extern/pybind11/docs/advanced/cast/eigen.rst new file mode 100644 index 000000000..a5c11a3f1 --- /dev/null +++ b/extern/pybind11/docs/advanced/cast/eigen.rst @@ -0,0 +1,310 @@ +Eigen +##### + +`Eigen `_ is C++ header-based library for dense and +sparse linear algebra. Due to its popularity and widespread adoption, pybind11 +provides transparent conversion and limited mapping support between Eigen and +Scientific Python linear algebra data types. + +To enable the built-in Eigen support you must include the optional header file +:file:`pybind11/eigen.h`. + +Pass-by-value +============= + +When binding a function with ordinary Eigen dense object arguments (for +example, ``Eigen::MatrixXd``), pybind11 will accept any input value that is +already (or convertible to) a ``numpy.ndarray`` with dimensions compatible with +the Eigen type, copy its values into a temporary Eigen variable of the +appropriate type, then call the function with this temporary variable. + +Sparse matrices are similarly copied to or from +``scipy.sparse.csr_matrix``/``scipy.sparse.csc_matrix`` objects. + +Pass-by-reference +================= + +One major limitation of the above is that every data conversion implicitly +involves a copy, which can be both expensive (for large matrices) and disallows +binding functions that change their (Matrix) arguments. Pybind11 allows you to +work around this by using Eigen's ``Eigen::Ref`` class much as you +would when writing a function taking a generic type in Eigen itself (subject to +some limitations discussed below). + +When calling a bound function accepting a ``Eigen::Ref`` +type, pybind11 will attempt to avoid copying by using an ``Eigen::Map`` object +that maps into the source ``numpy.ndarray`` data: this requires both that the +data types are the same (e.g. ``dtype='float64'`` and ``MatrixType::Scalar`` is +``double``); and that the storage is layout compatible. The latter limitation +is discussed in detail in the section below, and requires careful +consideration: by default, numpy matrices and Eigen matrices are *not* storage +compatible. + +If the numpy matrix cannot be used as is (either because its types differ, e.g. +passing an array of integers to an Eigen parameter requiring doubles, or +because the storage is incompatible), pybind11 makes a temporary copy and +passes the copy instead. + +When a bound function parameter is instead ``Eigen::Ref`` (note the +lack of ``const``), pybind11 will only allow the function to be called if it +can be mapped *and* if the numpy array is writeable (that is +``a.flags.writeable`` is true). Any access (including modification) made to +the passed variable will be transparently carried out directly on the +``numpy.ndarray``. + +This means you can write code such as the following and have it work as +expected: + +.. code-block:: cpp + + void scale_by_2(Eigen::Ref v) { + v *= 2; + } + +Note, however, that you will likely run into limitations due to numpy and +Eigen's difference default storage order for data; see the below section on +:ref:`storage_orders` for details on how to bind code that won't run into such +limitations. + +.. note:: + + Passing by reference is not supported for sparse types. + +Returning values to Python +========================== + +When returning an ordinary dense Eigen matrix type to numpy (e.g. +``Eigen::MatrixXd`` or ``Eigen::RowVectorXf``) pybind11 keeps the matrix and +returns a numpy array that directly references the Eigen matrix: no copy of the +data is performed. The numpy array will have ``array.flags.owndata`` set to +``False`` to indicate that it does not own the data, and the lifetime of the +stored Eigen matrix will be tied to the returned ``array``. + +If you bind a function with a non-reference, ``const`` return type (e.g. +``const Eigen::MatrixXd``), the same thing happens except that pybind11 also +sets the numpy array's ``writeable`` flag to false. + +If you return an lvalue reference or pointer, the usual pybind11 rules apply, +as dictated by the binding function's return value policy (see the +documentation on :ref:`return_value_policies` for full details). That means, +without an explicit return value policy, lvalue references will be copied and +pointers will be managed by pybind11. In order to avoid copying, you should +explicitly specify an appropriate return value policy, as in the following +example: + +.. code-block:: cpp + + class MyClass { + Eigen::MatrixXd big_mat = Eigen::MatrixXd::Zero(10000, 10000); + public: + Eigen::MatrixXd &getMatrix() { return big_mat; } + const Eigen::MatrixXd &viewMatrix() { return big_mat; } + }; + + // Later, in binding code: + py::class_(m, "MyClass") + .def(py::init<>()) + .def("copy_matrix", &MyClass::getMatrix) // Makes a copy! + .def("get_matrix", &MyClass::getMatrix, py::return_value_policy::reference_internal) + .def("view_matrix", &MyClass::viewMatrix, py::return_value_policy::reference_internal) + ; + +.. code-block:: python + + a = MyClass() + m = a.get_matrix() # flags.writeable = True, flags.owndata = False + v = a.view_matrix() # flags.writeable = False, flags.owndata = False + c = a.copy_matrix() # flags.writeable = True, flags.owndata = True + # m[5,6] and v[5,6] refer to the same element, c[5,6] does not. + +Note in this example that ``py::return_value_policy::reference_internal`` is +used to tie the life of the MyClass object to the life of the returned arrays. + +You may also return an ``Eigen::Ref``, ``Eigen::Map`` or other map-like Eigen +object (for example, the return value of ``matrix.block()`` and related +methods) that map into a dense Eigen type. When doing so, the default +behaviour of pybind11 is to simply reference the returned data: you must take +care to ensure that this data remains valid! You may ask pybind11 to +explicitly *copy* such a return value by using the +``py::return_value_policy::copy`` policy when binding the function. You may +also use ``py::return_value_policy::reference_internal`` or a +``py::keep_alive`` to ensure the data stays valid as long as the returned numpy +array does. + +When returning such a reference of map, pybind11 additionally respects the +readonly-status of the returned value, marking the numpy array as non-writeable +if the reference or map was itself read-only. + +.. note:: + + Sparse types are always copied when returned. + +.. _storage_orders: + +Storage orders +============== + +Passing arguments via ``Eigen::Ref`` has some limitations that you must be +aware of in order to effectively pass matrices by reference. First and +foremost is that the default ``Eigen::Ref`` class requires +contiguous storage along columns (for column-major types, the default in Eigen) +or rows if ``MatrixType`` is specifically an ``Eigen::RowMajor`` storage type. +The former, Eigen's default, is incompatible with ``numpy``'s default row-major +storage, and so you will not be able to pass numpy arrays to Eigen by reference +without making one of two changes. + +(Note that this does not apply to vectors (or column or row matrices): for such +types the "row-major" and "column-major" distinction is meaningless). + +The first approach is to change the use of ``Eigen::Ref`` to the +more general ``Eigen::Ref>`` (or similar type with a fully dynamic stride type in the +third template argument). Since this is a rather cumbersome type, pybind11 +provides a ``py::EigenDRef`` type alias for your convenience (along +with EigenDMap for the equivalent Map, and EigenDStride for just the stride +type). + +This type allows Eigen to map into any arbitrary storage order. This is not +the default in Eigen for performance reasons: contiguous storage allows +vectorization that cannot be done when storage is not known to be contiguous at +compile time. The default ``Eigen::Ref`` stride type allows non-contiguous +storage along the outer dimension (that is, the rows of a column-major matrix +or columns of a row-major matrix), but not along the inner dimension. + +This type, however, has the added benefit of also being able to map numpy array +slices. For example, the following (contrived) example uses Eigen with a numpy +slice to multiply by 2 all coefficients that are both on even rows (0, 2, 4, +...) and in columns 2, 5, or 8: + +.. code-block:: cpp + + m.def("scale", [](py::EigenDRef m, double c) { m *= c; }); + +.. code-block:: python + + # a = np.array(...) + scale_by_2(myarray[0::2, 2:9:3]) + +The second approach to avoid copying is more intrusive: rearranging the +underlying data types to not run into the non-contiguous storage problem in the +first place. In particular, that means using matrices with ``Eigen::RowMajor`` +storage, where appropriate, such as: + +.. code-block:: cpp + + using RowMatrixXd = Eigen::Matrix; + // Use RowMatrixXd instead of MatrixXd + +Now bound functions accepting ``Eigen::Ref`` arguments will be +callable with numpy's (default) arrays without involving a copying. + +You can, alternatively, change the storage order that numpy arrays use by +adding the ``order='F'`` option when creating an array: + +.. code-block:: python + + myarray = np.array(source, order="F") + +Such an object will be passable to a bound function accepting an +``Eigen::Ref`` (or similar column-major Eigen type). + +One major caveat with this approach, however, is that it is not entirely as +easy as simply flipping all Eigen or numpy usage from one to the other: some +operations may alter the storage order of a numpy array. For example, ``a2 = +array.transpose()`` results in ``a2`` being a view of ``array`` that references +the same data, but in the opposite storage order! + +While this approach allows fully optimized vectorized calculations in Eigen, it +cannot be used with array slices, unlike the first approach. + +When *returning* a matrix to Python (either a regular matrix, a reference via +``Eigen::Ref<>``, or a map/block into a matrix), no special storage +consideration is required: the created numpy array will have the required +stride that allows numpy to properly interpret the array, whatever its storage +order. + +Failing rather than copying +=========================== + +The default behaviour when binding ``Eigen::Ref`` Eigen +references is to copy matrix values when passed a numpy array that does not +conform to the element type of ``MatrixType`` or does not have a compatible +stride layout. If you want to explicitly avoid copying in such a case, you +should bind arguments using the ``py::arg().noconvert()`` annotation (as +described in the :ref:`nonconverting_arguments` documentation). + +The following example shows an example of arguments that don't allow data +copying to take place: + +.. code-block:: cpp + + // The method and function to be bound: + class MyClass { + // ... + double some_method(const Eigen::Ref &matrix) { /* ... */ } + }; + float some_function(const Eigen::Ref &big, + const Eigen::Ref &small) { + // ... + } + + // The associated binding code: + using namespace pybind11::literals; // for "arg"_a + py::class_(m, "MyClass") + // ... other class definitions + .def("some_method", &MyClass::some_method, py::arg().noconvert()); + + m.def("some_function", &some_function, + "big"_a.noconvert(), // <- Don't allow copying for this arg + "small"_a // <- This one can be copied if needed + ); + +With the above binding code, attempting to call the the ``some_method(m)`` +method on a ``MyClass`` object, or attempting to call ``some_function(m, m2)`` +will raise a ``RuntimeError`` rather than making a temporary copy of the array. +It will, however, allow the ``m2`` argument to be copied into a temporary if +necessary. + +Note that explicitly specifying ``.noconvert()`` is not required for *mutable* +Eigen references (e.g. ``Eigen::Ref`` without ``const`` on the +``MatrixXd``): mutable references will never be called with a temporary copy. + +Vectors versus column/row matrices +================================== + +Eigen and numpy have fundamentally different notions of a vector. In Eigen, a +vector is simply a matrix with the number of columns or rows set to 1 at +compile time (for a column vector or row vector, respectively). NumPy, in +contrast, has comparable 2-dimensional 1xN and Nx1 arrays, but *also* has +1-dimensional arrays of size N. + +When passing a 2-dimensional 1xN or Nx1 array to Eigen, the Eigen type must +have matching dimensions: That is, you cannot pass a 2-dimensional Nx1 numpy +array to an Eigen value expecting a row vector, or a 1xN numpy array as a +column vector argument. + +On the other hand, pybind11 allows you to pass 1-dimensional arrays of length N +as Eigen parameters. If the Eigen type can hold a column vector of length N it +will be passed as such a column vector. If not, but the Eigen type constraints +will accept a row vector, it will be passed as a row vector. (The column +vector takes precedence when both are supported, for example, when passing a +1D numpy array to a MatrixXd argument). Note that the type need not be +explicitly a vector: it is permitted to pass a 1D numpy array of size 5 to an +Eigen ``Matrix``: you would end up with a 1x5 Eigen matrix. +Passing the same to an ``Eigen::MatrixXd`` would result in a 5x1 Eigen matrix. + +When returning an Eigen vector to numpy, the conversion is ambiguous: a row +vector of length 4 could be returned as either a 1D array of length 4, or as a +2D array of size 1x4. When encountering such a situation, pybind11 compromises +by considering the returned Eigen type: if it is a compile-time vector--that +is, the type has either the number of rows or columns set to 1 at compile +time--pybind11 converts to a 1D numpy array when returning the value. For +instances that are a vector only at run-time (e.g. ``MatrixXd``, +``Matrix``), pybind11 returns the vector as a 2D array to +numpy. If this isn't want you want, you can use ``array.reshape(...)`` to get +a view of the same data in the desired dimensions. + +.. seealso:: + + The file :file:`tests/test_eigen.cpp` contains a complete example that + shows how to pass Eigen sparse and dense data types in more detail. diff --git a/extern/pybind11/docs/advanced/cast/functional.rst b/extern/pybind11/docs/advanced/cast/functional.rst new file mode 100644 index 000000000..d9b460575 --- /dev/null +++ b/extern/pybind11/docs/advanced/cast/functional.rst @@ -0,0 +1,109 @@ +Functional +########## + +The following features must be enabled by including :file:`pybind11/functional.h`. + + +Callbacks and passing anonymous functions +========================================= + +The C++11 standard brought lambda functions and the generic polymorphic +function wrapper ``std::function<>`` to the C++ programming language, which +enable powerful new ways of working with functions. Lambda functions come in +two flavors: stateless lambda function resemble classic function pointers that +link to an anonymous piece of code, while stateful lambda functions +additionally depend on captured variables that are stored in an anonymous +*lambda closure object*. + +Here is a simple example of a C++ function that takes an arbitrary function +(stateful or stateless) with signature ``int -> int`` as an argument and runs +it with the value 10. + +.. code-block:: cpp + + int func_arg(const std::function &f) { + return f(10); + } + +The example below is more involved: it takes a function of signature ``int -> int`` +and returns another function of the same kind. The return value is a stateful +lambda function, which stores the value ``f`` in the capture object and adds 1 to +its return value upon execution. + +.. code-block:: cpp + + std::function func_ret(const std::function &f) { + return [f](int i) { + return f(i) + 1; + }; + } + +This example demonstrates using python named parameters in C++ callbacks which +requires using ``py::cpp_function`` as a wrapper. Usage is similar to defining +methods of classes: + +.. code-block:: cpp + + py::cpp_function func_cpp() { + return py::cpp_function([](int i) { return i+1; }, + py::arg("number")); + } + +After including the extra header file :file:`pybind11/functional.h`, it is almost +trivial to generate binding code for all of these functions. + +.. code-block:: cpp + + #include + + PYBIND11_MODULE(example, m) { + m.def("func_arg", &func_arg); + m.def("func_ret", &func_ret); + m.def("func_cpp", &func_cpp); + } + +The following interactive session shows how to call them from Python. + +.. code-block:: pycon + + $ python + >>> import example + >>> def square(i): + ... return i * i + ... + >>> example.func_arg(square) + 100L + >>> square_plus_1 = example.func_ret(square) + >>> square_plus_1(4) + 17L + >>> plus_1 = func_cpp() + >>> plus_1(number=43) + 44L + +.. warning:: + + Keep in mind that passing a function from C++ to Python (or vice versa) + will instantiate a piece of wrapper code that translates function + invocations between the two languages. Naturally, this translation + increases the computational cost of each function call somewhat. A + problematic situation can arise when a function is copied back and forth + between Python and C++ many times in a row, in which case the underlying + wrappers will accumulate correspondingly. The resulting long sequence of + C++ -> Python -> C++ -> ... roundtrips can significantly decrease + performance. + + There is one exception: pybind11 detects case where a stateless function + (i.e. a function pointer or a lambda function without captured variables) + is passed as an argument to another C++ function exposed in Python. In this + case, there is no overhead. Pybind11 will extract the underlying C++ + function pointer from the wrapped function to sidestep a potential C++ -> + Python -> C++ roundtrip. This is demonstrated in :file:`tests/test_callbacks.cpp`. + +.. note:: + + This functionality is very useful when generating bindings for callbacks in + C++ libraries (e.g. GUI libraries, asynchronous networking libraries, etc.). + + The file :file:`tests/test_callbacks.cpp` contains a complete example + that demonstrates how to work with callbacks and anonymous functions in + more detail. diff --git a/extern/pybind11/docs/advanced/cast/index.rst b/extern/pybind11/docs/advanced/cast/index.rst new file mode 100644 index 000000000..3ce9ea028 --- /dev/null +++ b/extern/pybind11/docs/advanced/cast/index.rst @@ -0,0 +1,43 @@ +.. _type-conversions: + +Type conversions +################ + +Apart from enabling cross-language function calls, a fundamental problem +that a binding tool like pybind11 must address is to provide access to +native Python types in C++ and vice versa. There are three fundamentally +different ways to do this—which approach is preferable for a particular type +depends on the situation at hand. + +1. Use a native C++ type everywhere. In this case, the type must be wrapped + using pybind11-generated bindings so that Python can interact with it. + +2. Use a native Python type everywhere. It will need to be wrapped so that + C++ functions can interact with it. + +3. Use a native C++ type on the C++ side and a native Python type on the + Python side. pybind11 refers to this as a *type conversion*. + + Type conversions are the most "natural" option in the sense that native + (non-wrapped) types are used everywhere. The main downside is that a copy + of the data must be made on every Python ↔ C++ transition: this is + needed since the C++ and Python versions of the same type generally won't + have the same memory layout. + + pybind11 can perform many kinds of conversions automatically. An overview + is provided in the table ":ref:`conversion_table`". + +The following subsections discuss the differences between these options in more +detail. The main focus in this section is on type conversions, which represent +the last case of the above list. + +.. toctree:: + :maxdepth: 1 + + overview + strings + stl + functional + chrono + eigen + custom diff --git a/extern/pybind11/docs/advanced/cast/overview.rst b/extern/pybind11/docs/advanced/cast/overview.rst new file mode 100644 index 000000000..011bd4c7a --- /dev/null +++ b/extern/pybind11/docs/advanced/cast/overview.rst @@ -0,0 +1,170 @@ +Overview +######## + +.. rubric:: 1. Native type in C++, wrapper in Python + +Exposing a custom C++ type using :class:`py::class_` was covered in detail +in the :doc:`/classes` section. There, the underlying data structure is +always the original C++ class while the :class:`py::class_` wrapper provides +a Python interface. Internally, when an object like this is sent from C++ to +Python, pybind11 will just add the outer wrapper layer over the native C++ +object. Getting it back from Python is just a matter of peeling off the +wrapper. + +.. rubric:: 2. Wrapper in C++, native type in Python + +This is the exact opposite situation. Now, we have a type which is native to +Python, like a ``tuple`` or a ``list``. One way to get this data into C++ is +with the :class:`py::object` family of wrappers. These are explained in more +detail in the :doc:`/advanced/pycpp/object` section. We'll just give a quick +example here: + +.. code-block:: cpp + + void print_list(py::list my_list) { + for (auto item : my_list) + std::cout << item << " "; + } + +.. code-block:: pycon + + >>> print_list([1, 2, 3]) + 1 2 3 + +The Python ``list`` is not converted in any way -- it's just wrapped in a C++ +:class:`py::list` class. At its core it's still a Python object. Copying a +:class:`py::list` will do the usual reference-counting like in Python. +Returning the object to Python will just remove the thin wrapper. + +.. rubric:: 3. Converting between native C++ and Python types + +In the previous two cases we had a native type in one language and a wrapper in +the other. Now, we have native types on both sides and we convert between them. + +.. code-block:: cpp + + void print_vector(const std::vector &v) { + for (auto item : v) + std::cout << item << "\n"; + } + +.. code-block:: pycon + + >>> print_vector([1, 2, 3]) + 1 2 3 + +In this case, pybind11 will construct a new ``std::vector`` and copy each +element from the Python ``list``. The newly constructed object will be passed +to ``print_vector``. The same thing happens in the other direction: a new +``list`` is made to match the value returned from C++. + +Lots of these conversions are supported out of the box, as shown in the table +below. They are very convenient, but keep in mind that these conversions are +fundamentally based on copying data. This is perfectly fine for small immutable +types but it may become quite expensive for large data structures. This can be +avoided by overriding the automatic conversion with a custom wrapper (i.e. the +above-mentioned approach 1). This requires some manual effort and more details +are available in the :ref:`opaque` section. + +.. _conversion_table: + +List of all builtin conversions +------------------------------- + +The following basic data types are supported out of the box (some may require +an additional extension header to be included). To pass other data structures +as arguments and return values, refer to the section on binding :ref:`classes`. + ++------------------------------------+---------------------------+-----------------------------------+ +| Data type | Description | Header file | ++====================================+===========================+===================================+ +| ``int8_t``, ``uint8_t`` | 8-bit integers | :file:`pybind11/pybind11.h` | ++------------------------------------+---------------------------+-----------------------------------+ +| ``int16_t``, ``uint16_t`` | 16-bit integers | :file:`pybind11/pybind11.h` | ++------------------------------------+---------------------------+-----------------------------------+ +| ``int32_t``, ``uint32_t`` | 32-bit integers | :file:`pybind11/pybind11.h` | ++------------------------------------+---------------------------+-----------------------------------+ +| ``int64_t``, ``uint64_t`` | 64-bit integers | :file:`pybind11/pybind11.h` | ++------------------------------------+---------------------------+-----------------------------------+ +| ``ssize_t``, ``size_t`` | Platform-dependent size | :file:`pybind11/pybind11.h` | ++------------------------------------+---------------------------+-----------------------------------+ +| ``float``, ``double`` | Floating point types | :file:`pybind11/pybind11.h` | ++------------------------------------+---------------------------+-----------------------------------+ +| ``bool`` | Two-state Boolean type | :file:`pybind11/pybind11.h` | ++------------------------------------+---------------------------+-----------------------------------+ +| ``char`` | Character literal | :file:`pybind11/pybind11.h` | ++------------------------------------+---------------------------+-----------------------------------+ +| ``char16_t`` | UTF-16 character literal | :file:`pybind11/pybind11.h` | ++------------------------------------+---------------------------+-----------------------------------+ +| ``char32_t`` | UTF-32 character literal | :file:`pybind11/pybind11.h` | ++------------------------------------+---------------------------+-----------------------------------+ +| ``wchar_t`` | Wide character literal | :file:`pybind11/pybind11.h` | ++------------------------------------+---------------------------+-----------------------------------+ +| ``const char *`` | UTF-8 string literal | :file:`pybind11/pybind11.h` | ++------------------------------------+---------------------------+-----------------------------------+ +| ``const char16_t *`` | UTF-16 string literal | :file:`pybind11/pybind11.h` | ++------------------------------------+---------------------------+-----------------------------------+ +| ``const char32_t *`` | UTF-32 string literal | :file:`pybind11/pybind11.h` | ++------------------------------------+---------------------------+-----------------------------------+ +| ``const wchar_t *`` | Wide string literal | :file:`pybind11/pybind11.h` | ++------------------------------------+---------------------------+-----------------------------------+ +| ``std::string`` | STL dynamic UTF-8 string | :file:`pybind11/pybind11.h` | ++------------------------------------+---------------------------+-----------------------------------+ +| ``std::u16string`` | STL dynamic UTF-16 string | :file:`pybind11/pybind11.h` | ++------------------------------------+---------------------------+-----------------------------------+ +| ``std::u32string`` | STL dynamic UTF-32 string | :file:`pybind11/pybind11.h` | ++------------------------------------+---------------------------+-----------------------------------+ +| ``std::wstring`` | STL dynamic wide string | :file:`pybind11/pybind11.h` | ++------------------------------------+---------------------------+-----------------------------------+ +| ``std::string_view``, | STL C++17 string views | :file:`pybind11/pybind11.h` | +| ``std::u16string_view``, etc. | | | ++------------------------------------+---------------------------+-----------------------------------+ +| ``std::pair`` | Pair of two custom types | :file:`pybind11/pybind11.h` | ++------------------------------------+---------------------------+-----------------------------------+ +| ``std::tuple<...>`` | Arbitrary tuple of types | :file:`pybind11/pybind11.h` | ++------------------------------------+---------------------------+-----------------------------------+ +| ``std::reference_wrapper<...>`` | Reference type wrapper | :file:`pybind11/pybind11.h` | ++------------------------------------+---------------------------+-----------------------------------+ +| ``std::complex`` | Complex numbers | :file:`pybind11/complex.h` | ++------------------------------------+---------------------------+-----------------------------------+ +| ``std::array`` | STL static array | :file:`pybind11/stl.h` | ++------------------------------------+---------------------------+-----------------------------------+ +| ``std::vector`` | STL dynamic array | :file:`pybind11/stl.h` | ++------------------------------------+---------------------------+-----------------------------------+ +| ``std::deque`` | STL double-ended queue | :file:`pybind11/stl.h` | ++------------------------------------+---------------------------+-----------------------------------+ +| ``std::valarray`` | STL value array | :file:`pybind11/stl.h` | ++------------------------------------+---------------------------+-----------------------------------+ +| ``std::list`` | STL linked list | :file:`pybind11/stl.h` | ++------------------------------------+---------------------------+-----------------------------------+ +| ``std::map`` | STL ordered map | :file:`pybind11/stl.h` | ++------------------------------------+---------------------------+-----------------------------------+ +| ``std::unordered_map`` | STL unordered map | :file:`pybind11/stl.h` | ++------------------------------------+---------------------------+-----------------------------------+ +| ``std::set`` | STL ordered set | :file:`pybind11/stl.h` | ++------------------------------------+---------------------------+-----------------------------------+ +| ``std::unordered_set`` | STL unordered set | :file:`pybind11/stl.h` | ++------------------------------------+---------------------------+-----------------------------------+ +| ``std::optional`` | STL optional type (C++17) | :file:`pybind11/stl.h` | ++------------------------------------+---------------------------+-----------------------------------+ +| ``std::experimental::optional`` | STL optional type (exp.) | :file:`pybind11/stl.h` | ++------------------------------------+---------------------------+-----------------------------------+ +| ``std::variant<...>`` | Type-safe union (C++17) | :file:`pybind11/stl.h` | ++------------------------------------+---------------------------+-----------------------------------+ +| ``std::filesystem::path`` | STL path (C++17) [#]_ | :file:`pybind11/stl/filesystem.h` | ++------------------------------------+---------------------------+-----------------------------------+ +| ``std::function<...>`` | STL polymorphic function | :file:`pybind11/functional.h` | ++------------------------------------+---------------------------+-----------------------------------+ +| ``std::chrono::duration<...>`` | STL time duration | :file:`pybind11/chrono.h` | ++------------------------------------+---------------------------+-----------------------------------+ +| ``std::chrono::time_point<...>`` | STL date/time | :file:`pybind11/chrono.h` | ++------------------------------------+---------------------------+-----------------------------------+ +| ``Eigen::Matrix<...>`` | Eigen: dense matrix | :file:`pybind11/eigen.h` | ++------------------------------------+---------------------------+-----------------------------------+ +| ``Eigen::Map<...>`` | Eigen: mapped memory | :file:`pybind11/eigen.h` | ++------------------------------------+---------------------------+-----------------------------------+ +| ``Eigen::SparseMatrix<...>`` | Eigen: sparse matrix | :file:`pybind11/eigen.h` | ++------------------------------------+---------------------------+-----------------------------------+ + +.. [#] ``std::filesystem::path`` is converted to ``pathlib.Path`` and + ``os.PathLike`` is converted to ``std::filesystem::path``. diff --git a/extern/pybind11/docs/advanced/cast/stl.rst b/extern/pybind11/docs/advanced/cast/stl.rst new file mode 100644 index 000000000..03d49b295 --- /dev/null +++ b/extern/pybind11/docs/advanced/cast/stl.rst @@ -0,0 +1,249 @@ +STL containers +############## + +Automatic conversion +==================== + +When including the additional header file :file:`pybind11/stl.h`, conversions +between ``std::vector<>``/``std::deque<>``/``std::list<>``/``std::array<>``/``std::valarray<>``, +``std::set<>``/``std::unordered_set<>``, and +``std::map<>``/``std::unordered_map<>`` and the Python ``list``, ``set`` and +``dict`` data structures are automatically enabled. The types ``std::pair<>`` +and ``std::tuple<>`` are already supported out of the box with just the core +:file:`pybind11/pybind11.h` header. + +The major downside of these implicit conversions is that containers must be +converted (i.e. copied) on every Python->C++ and C++->Python transition, which +can have implications on the program semantics and performance. Please read the +next sections for more details and alternative approaches that avoid this. + +.. note:: + + Arbitrary nesting of any of these types is possible. + +.. seealso:: + + The file :file:`tests/test_stl.cpp` contains a complete + example that demonstrates how to pass STL data types in more detail. + +.. _cpp17_container_casters: + +C++17 library containers +======================== + +The :file:`pybind11/stl.h` header also includes support for ``std::optional<>`` +and ``std::variant<>``. These require a C++17 compiler and standard library. +In C++14 mode, ``std::experimental::optional<>`` is supported if available. + +Various versions of these containers also exist for C++11 (e.g. in Boost). +pybind11 provides an easy way to specialize the ``type_caster`` for such +types: + +.. code-block:: cpp + + // `boost::optional` as an example -- can be any `std::optional`-like container + namespace PYBIND11_NAMESPACE { namespace detail { + template + struct type_caster> : optional_caster> {}; + }} + +The above should be placed in a header file and included in all translation units +where automatic conversion is needed. Similarly, a specialization can be provided +for custom variant types: + +.. code-block:: cpp + + // `boost::variant` as an example -- can be any `std::variant`-like container + namespace PYBIND11_NAMESPACE { namespace detail { + template + struct type_caster> : variant_caster> {}; + + // Specifies the function used to visit the variant -- `apply_visitor` instead of `visit` + template <> + struct visit_helper { + template + static auto call(Args &&...args) -> decltype(boost::apply_visitor(args...)) { + return boost::apply_visitor(args...); + } + }; + }} // 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 +included to tell pybind11 how to visit the variant. + +.. warning:: + + When converting a ``variant`` type, pybind11 follows the same rules as when + determining which function overload to call (:ref:`overload_resolution`), and + so the same caveats hold. In particular, the order in which the ``variant``'s + alternatives are listed is important, since pybind11 will try conversions in + this order. This means that, for example, when converting ``variant``, + the ``bool`` variant will never be selected, as any Python ``bool`` is already + an ``int`` and is convertible to a C++ ``int``. Changing the order of alternatives + (and using ``variant``, in this example) provides a solution. + +.. note:: + + pybind11 only supports the modern implementation of ``boost::variant`` + which makes use of variadic templates. This requires Boost 1.56 or newer. + +.. _opaque: + +Making opaque types +=================== + +pybind11 heavily relies on a template matching mechanism to convert parameters +and return values that are constructed from STL data types such as vectors, +linked lists, hash tables, etc. This even works in a recursive manner, for +instance to deal with lists of hash maps of pairs of elementary and custom +types, etc. + +However, a fundamental limitation of this approach is that internal conversions +between Python and C++ types involve a copy operation that prevents +pass-by-reference semantics. What does this mean? + +Suppose we bind the following function + +.. code-block:: cpp + + void append_1(std::vector &v) { + v.push_back(1); + } + +and call it from Python, the following happens: + +.. code-block:: pycon + + >>> v = [5, 6] + >>> append_1(v) + >>> print(v) + [5, 6] + +As you can see, when passing STL data structures by reference, modifications +are not propagated back the Python side. A similar situation arises when +exposing STL data structures using the ``def_readwrite`` or ``def_readonly`` +functions: + +.. code-block:: cpp + + /* ... definition ... */ + + class MyClass { + std::vector contents; + }; + + /* ... binding code ... */ + + py::class_(m, "MyClass") + .def(py::init<>()) + .def_readwrite("contents", &MyClass::contents); + +In this case, properties can be read and written in their entirety. However, an +``append`` operation involving such a list type has no effect: + +.. code-block:: pycon + + >>> m = MyClass() + >>> m.contents = [5, 6] + >>> print(m.contents) + [5, 6] + >>> m.contents.append(7) + >>> print(m.contents) + [5, 6] + +Finally, the involved copy operations can be costly when dealing with very +large lists. To deal with all of the above situations, pybind11 provides a +macro named ``PYBIND11_MAKE_OPAQUE(T)`` that disables the template-based +conversion machinery of types, thus rendering them *opaque*. The contents of +opaque objects are never inspected or extracted, hence they *can* be passed by +reference. For instance, to turn ``std::vector`` into an opaque type, add +the declaration + +.. code-block:: cpp + + PYBIND11_MAKE_OPAQUE(std::vector); + +before any binding code (e.g. invocations to ``class_::def()``, etc.). This +macro must be specified at the top level (and outside of any namespaces), since +it adds a template instantiation of ``type_caster``. If your binding code consists of +multiple compilation units, it must be present in every file (typically via a +common header) preceding any usage of ``std::vector``. Opaque types must +also have a corresponding ``class_`` declaration to associate them with a name +in Python, and to define a set of available operations, e.g.: + +.. code-block:: cpp + + py::class_>(m, "IntVector") + .def(py::init<>()) + .def("clear", &std::vector::clear) + .def("pop_back", &std::vector::pop_back) + .def("__len__", [](const std::vector &v) { return v.size(); }) + .def("__iter__", [](std::vector &v) { + return py::make_iterator(v.begin(), v.end()); + }, py::keep_alive<0, 1>()) /* Keep vector alive while iterator is used */ + // .... + +.. seealso:: + + The file :file:`tests/test_opaque_types.cpp` contains a complete + example that demonstrates how to create and expose opaque types using + pybind11 in more detail. + +.. _stl_bind: + +Binding STL containers +====================== + +The ability to expose STL containers as native Python objects is a fairly +common request, hence pybind11 also provides an optional header file named +:file:`pybind11/stl_bind.h` that does exactly this. The mapped containers try +to match the behavior of their native Python counterparts as much as possible. + +The following example showcases usage of :file:`pybind11/stl_bind.h`: + +.. code-block:: cpp + + // Don't forget this + #include + + PYBIND11_MAKE_OPAQUE(std::vector); + PYBIND11_MAKE_OPAQUE(std::map); + + // ... + + // later in binding code: + py::bind_vector>(m, "VectorInt"); + py::bind_map>(m, "MapStringDouble"); + +When binding STL containers pybind11 considers the types of the container's +elements to decide whether the container should be confined to the local module +(via the :ref:`module_local` feature). If the container element types are +anything other than already-bound custom types bound without +``py::module_local()`` the container binding will have ``py::module_local()`` +applied. This includes converting types such as numeric types, strings, Eigen +types; and types that have not yet been bound at the time of the stl container +binding. This module-local binding is designed to avoid potential conflicts +between module bindings (for example, from two separate modules each attempting +to bind ``std::vector`` as a python type). + +It is possible to override this behavior to force a definition to be either +module-local or global. To do so, you can pass the attributes +``py::module_local()`` (to make the binding module-local) or +``py::module_local(false)`` (to make the binding global) into the +``py::bind_vector`` or ``py::bind_map`` arguments: + +.. code-block:: cpp + + py::bind_vector>(m, "VectorInt", py::module_local(false)); + +Note, however, that such a global binding would make it impossible to load this +module at the same time as any other pybind module that also attempts to bind +the same container type (``std::vector`` in the above example). + +See :ref:`module_local` for more details on module-local bindings. + +.. seealso:: + + The file :file:`tests/test_stl_binders.cpp` shows how to use the + convenience STL container wrappers. diff --git a/extern/pybind11/docs/advanced/cast/strings.rst b/extern/pybind11/docs/advanced/cast/strings.rst new file mode 100644 index 000000000..271716b4b --- /dev/null +++ b/extern/pybind11/docs/advanced/cast/strings.rst @@ -0,0 +1,296 @@ +Strings, bytes and Unicode conversions +###################################### + +Passing Python strings to C++ +============================= + +When a Python ``str`` is passed from Python to a C++ function that accepts +``std::string`` or ``char *`` as arguments, pybind11 will encode the Python +string to UTF-8. All Python ``str`` can be encoded in UTF-8, so this operation +does not fail. + +The C++ language is encoding agnostic. It is the responsibility of the +programmer to track encodings. It's often easiest to simply `use UTF-8 +everywhere `_. + +.. code-block:: c++ + + m.def("utf8_test", + [](const std::string &s) { + cout << "utf-8 is icing on the cake.\n"; + cout << s; + } + ); + m.def("utf8_charptr", + [](const char *s) { + cout << "My favorite food is\n"; + cout << s; + } + ); + +.. code-block:: pycon + + >>> utf8_test("🎂") + utf-8 is icing on the cake. + 🎂 + + >>> utf8_charptr("🍕") + My favorite food is + 🍕 + +.. note:: + + Some terminal emulators do not support UTF-8 or emoji fonts and may not + display the example above correctly. + +The results are the same whether the C++ function accepts arguments by value or +reference, and whether or not ``const`` is used. + +Passing bytes to C++ +-------------------- + +A Python ``bytes`` object will be passed to C++ functions that accept +``std::string`` or ``char*`` *without* conversion. In order to make a function +*only* accept ``bytes`` (and not ``str``), declare it as taking a ``py::bytes`` +argument. + + +Returning C++ strings to Python +=============================== + +When a C++ function returns a ``std::string`` or ``char*`` to a Python caller, +**pybind11 will assume that the string is valid UTF-8** and will decode it to a +native Python ``str``, using the same API as Python uses to perform +``bytes.decode('utf-8')``. If this implicit conversion fails, pybind11 will +raise a ``UnicodeDecodeError``. + +.. code-block:: c++ + + m.def("std_string_return", + []() { + return std::string("This string needs to be UTF-8 encoded"); + } + ); + +.. code-block:: pycon + + >>> isinstance(example.std_string_return(), str) + True + + +Because UTF-8 is inclusive of pure ASCII, there is never any issue with +returning a pure ASCII string to Python. If there is any possibility that the +string is not pure ASCII, it is necessary to ensure the encoding is valid +UTF-8. + +.. warning:: + + Implicit conversion assumes that a returned ``char *`` is null-terminated. + If there is no null terminator a buffer overrun will occur. + +Explicit conversions +-------------------- + +If some C++ code constructs a ``std::string`` that is not a UTF-8 string, one +can perform a explicit conversion and return a ``py::str`` object. Explicit +conversion has the same overhead as implicit conversion. + +.. code-block:: c++ + + // This uses the Python C API to convert Latin-1 to Unicode + m.def("str_output", + []() { + std::string s = "Send your r\xe9sum\xe9 to Alice in HR"; // Latin-1 + 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); + } + ); + +.. code-block:: pycon + + >>> str_output() + 'Send your résumé to Alice in HR' + +The `Python C API +`_ provides +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 +to UTF-8. + +Return C++ strings without conversion +------------------------------------- + +If the data in a C++ ``std::string`` does not represent text and should be +returned to Python as ``bytes``, then one can return the data as a +``py::bytes`` object. + +.. code-block:: c++ + + m.def("return_bytes", + []() { + std::string s("\xba\xd0\xba\xd0"); // Not valid UTF-8 + return py::bytes(s); // Return the data without transcoding + } + ); + +.. code-block:: pycon + + >>> example.return_bytes() + b'\xba\xd0\xba\xd0' + + +Note the asymmetry: pybind11 will convert ``bytes`` to ``std::string`` without +encoding, but cannot convert ``std::string`` back to ``bytes`` implicitly. + +.. code-block:: c++ + + m.def("asymmetry", + [](std::string s) { // Accepts str or bytes from Python + return s; // Looks harmless, but implicitly converts to str + } + ); + +.. code-block:: pycon + + >>> isinstance(example.asymmetry(b"have some bytes"), str) + True + + >>> example.asymmetry(b"\xba\xd0\xba\xd0") # invalid utf-8 as bytes + UnicodeDecodeError: 'utf-8' codec can't decode byte 0xba in position 0: invalid start byte + + +Wide character strings +====================== + +When a Python ``str`` is passed to a C++ function expecting ``std::wstring``, +``wchar_t*``, ``std::u16string`` or ``std::u32string``, the ``str`` will be +encoded to UTF-16 or UTF-32 depending on how the C++ compiler implements each +type, in the platform's native endianness. When strings of these types are +returned, they are assumed to contain valid UTF-16 or UTF-32, and will be +decoded to Python ``str``. + +.. code-block:: c++ + + #define UNICODE + #include + + m.def("set_window_text", + [](HWND hwnd, std::wstring s) { + // Call SetWindowText with null-terminated UTF-16 string + ::SetWindowText(hwnd, s.c_str()); + } + ); + m.def("get_window_text", + [](HWND hwnd) { + const int buffer_size = ::GetWindowTextLength(hwnd) + 1; + auto buffer = std::make_unique< wchar_t[] >(buffer_size); + + ::GetWindowText(hwnd, buffer.data(), buffer_size); + + std::wstring text(buffer.get()); + + // wstring will be converted to Python str + return text; + } + ); + +Strings in multibyte encodings such as Shift-JIS must transcoded to a +UTF-8/16/32 before being returned to Python. + + +Character literals +================== + +C++ functions that accept character literals as input will receive the first +character of a Python ``str`` as their input. If the string is longer than one +Unicode character, trailing characters will be ignored. + +When a character literal is returned from C++ (such as a ``char`` or a +``wchar_t``), it will be converted to a ``str`` that represents the single +character. + +.. code-block:: c++ + + m.def("pass_char", [](char c) { return c; }); + m.def("pass_wchar", [](wchar_t w) { return w; }); + +.. code-block:: pycon + + >>> example.pass_char("A") + 'A' + +While C++ will cast integers to character types (``char c = 0x65;``), pybind11 +does not convert Python integers to characters implicitly. The Python function +``chr()`` can be used to convert integers to characters. + +.. code-block:: pycon + + >>> example.pass_char(0x65) + TypeError + + >>> example.pass_char(chr(0x65)) + 'A' + +If the desire is to work with an 8-bit integer, use ``int8_t`` or ``uint8_t`` +as the argument type. + +Grapheme clusters +----------------- + +A single grapheme may be represented by two or more Unicode characters. For +example 'é' is usually represented as U+00E9 but can also be expressed as the +combining character sequence U+0065 U+0301 (that is, the letter 'e' followed by +a combining acute accent). The combining character will be lost if the +two-character sequence is passed as an argument, even though it renders as a +single grapheme. + +.. code-block:: pycon + + >>> example.pass_wchar("é") + 'é' + + >>> combining_e_acute = "e" + "\u0301" + + >>> combining_e_acute + 'é' + + >>> combining_e_acute == "é" + False + + >>> example.pass_wchar(combining_e_acute) + 'e' + +Normalizing combining characters before passing the character literal to C++ +may resolve *some* of these issues: + +.. code-block:: pycon + + >>> example.pass_wchar(unicodedata.normalize("NFC", combining_e_acute)) + 'é' + +In some languages (Thai for example), there are `graphemes that cannot be +expressed as a single Unicode code point +`_, so there is +no way to capture them in a C++ character type. + + +C++17 string views +================== + +C++17 string views are automatically supported when compiling in C++17 mode. +They follow the same rules for encoding and decoding as the corresponding STL +string type (for example, a ``std::u16string_view`` argument will be passed +UTF-16-encoded data, and a returned ``std::string_view`` will be decoded as +UTF-8). + +References +========== + +* `The Absolute Minimum Every Software Developer Absolutely, Positively Must Know About Unicode and Character Sets (No Excuses!) `_ +* `C++ - Using STL Strings at Win32 API Boundaries `_ diff --git a/extern/pybind11/docs/advanced/classes.rst b/extern/pybind11/docs/advanced/classes.rst new file mode 100644 index 000000000..01a490b72 --- /dev/null +++ b/extern/pybind11/docs/advanced/classes.rst @@ -0,0 +1,1335 @@ +Classes +####### + +This section presents advanced binding code for classes and it is assumed +that you are already familiar with the basics from :doc:`/classes`. + +.. _overriding_virtuals: + +Overriding virtual functions in Python +====================================== + +Suppose that a C++ class or interface has a virtual function that we'd like +to override from within Python (we'll focus on the class ``Animal``; ``Dog`` is +given as a specific example of how one would do this with traditional C++ +code). + +.. code-block:: cpp + + class Animal { + public: + virtual ~Animal() { } + virtual std::string go(int n_times) = 0; + }; + + class Dog : public Animal { + public: + std::string go(int n_times) override { + std::string result; + for (int i=0; igo(3); + } + +Normally, the binding code for these classes would look as follows: + +.. code-block:: cpp + + PYBIND11_MODULE(example, m) { + py::class_(m, "Animal") + .def("go", &Animal::go); + + py::class_(m, "Dog") + .def(py::init<>()); + + m.def("call_go", &call_go); + } + +However, these bindings are impossible to extend: ``Animal`` is not +constructible, and we clearly require some kind of "trampoline" that +redirects virtual calls back to Python. + +Defining a new type of ``Animal`` from within Python is possible but requires a +helper class that is defined as follows: + +.. code-block:: cpp + + class PyAnimal : public Animal { + public: + /* Inherit the constructors */ + using Animal::Animal; + + /* Trampoline (need one for each virtual function) */ + std::string go(int n_times) override { + PYBIND11_OVERRIDE_PURE( + std::string, /* Return type */ + Animal, /* Parent class */ + go, /* Name of function in C++ (must match Python name) */ + n_times /* Argument(s) */ + ); + } + }; + +The macro :c:macro:`PYBIND11_OVERRIDE_PURE` should be used for pure virtual +functions, and :c:macro:`PYBIND11_OVERRIDE` should be used for functions which have +a default implementation. There are also two alternate macros +:c:macro:`PYBIND11_OVERRIDE_PURE_NAME` and :c:macro:`PYBIND11_OVERRIDE_NAME` which +take a string-valued name argument between the *Parent class* and *Name of the +function* slots, which defines the name of function in Python. This is required +when the C++ and Python versions of the +function have different names, e.g. ``operator()`` vs ``__call__``. + +The binding code also needs a few minor adaptations (highlighted): + +.. code-block:: cpp + :emphasize-lines: 2,3 + + PYBIND11_MODULE(example, m) { + py::class_(m, "Animal") + .def(py::init<>()) + .def("go", &Animal::go); + + py::class_(m, "Dog") + .def(py::init<>()); + + m.def("call_go", &call_go); + } + +Importantly, pybind11 is made aware of the trampoline helper class by +specifying it as an extra template argument to :class:`class_`. (This can also +be combined with other template arguments such as a custom holder type; the +order of template types does not matter). Following this, we are able to +define a constructor as usual. + +Bindings should be made against the actual class, not the trampoline helper class. + +.. code-block:: cpp + :emphasize-lines: 3 + + py::class_(m, "Animal"); + .def(py::init<>()) + .def("go", &PyAnimal::go); /* <--- THIS IS WRONG, use &Animal::go */ + +Note, however, that the above is sufficient for allowing python classes to +extend ``Animal``, but not ``Dog``: see :ref:`virtual_and_inheritance` for the +necessary steps required to providing proper overriding support for inherited +classes. + +The Python session below shows how to override ``Animal::go`` and invoke it via +a virtual method call. + +.. code-block:: pycon + + >>> from example import * + >>> d = Dog() + >>> call_go(d) + 'woof! woof! woof! ' + >>> class Cat(Animal): + ... def go(self, n_times): + ... return "meow! " * n_times + ... + >>> c = Cat() + >>> call_go(c) + 'meow! meow! meow! ' + +If you are defining a custom constructor in a derived Python class, you *must* +ensure that you explicitly call the bound C++ constructor using ``__init__``, +*regardless* of whether it is a default constructor or not. Otherwise, the +memory for the C++ portion of the instance will be left uninitialized, which +will generally leave the C++ instance in an invalid state and cause undefined +behavior if the C++ instance is subsequently used. + +.. versionchanged:: 2.6 + The default pybind11 metaclass will throw a ``TypeError`` when it detects + that ``__init__`` was not called by a derived class. + +Here is an example: + +.. code-block:: python + + class Dachshund(Dog): + def __init__(self, name): + Dog.__init__(self) # Without this, a TypeError is raised. + self.name = name + + def bark(self): + return "yap!" + +Note that a direct ``__init__`` constructor *should be called*, and ``super()`` +should not be used. For simple cases of linear inheritance, ``super()`` +may work, but once you begin mixing Python and C++ multiple inheritance, +things will fall apart due to differences between Python's MRO and C++'s +mechanisms. + +Please take a look at the :ref:`macro_notes` before using this feature. + +.. note:: + + When the overridden type returns a reference or pointer to a type that + pybind11 converts from Python (for example, numeric values, std::string, + and other built-in value-converting types), there are some limitations to + be aware of: + + - because in these cases there is no C++ variable to reference (the value + is stored in the referenced Python variable), pybind11 provides one in + the PYBIND11_OVERRIDE macros (when needed) with static storage duration. + Note that this means that invoking the overridden method on *any* + instance will change the referenced value stored in *all* instances of + that type. + + - Attempts to modify a non-const reference will not have the desired + effect: it will change only the static cache variable, but this change + will not propagate to underlying Python instance, and the change will be + replaced the next time the override is invoked. + +.. warning:: + + The :c:macro:`PYBIND11_OVERRIDE` and accompanying macros used to be called + ``PYBIND11_OVERLOAD`` up until pybind11 v2.5.0, and :func:`get_override` + used to be called ``get_overload``. This naming was corrected and the older + macro and function names may soon be deprecated, in order to reduce + confusion with overloaded functions and methods and ``py::overload_cast`` + (see :ref:`classes`). + +.. seealso:: + + The file :file:`tests/test_virtual_functions.cpp` contains a complete + example that demonstrates how to override virtual functions using pybind11 + in more detail. + +.. _virtual_and_inheritance: + +Combining virtual functions and inheritance +=========================================== + +When combining virtual methods with inheritance, you need to be sure to provide +an override for each method for which you want to allow overrides from derived +python classes. For example, suppose we extend the above ``Animal``/``Dog`` +example as follows: + +.. code-block:: cpp + + class Animal { + public: + virtual std::string go(int n_times) = 0; + virtual std::string name() { return "unknown"; } + }; + class Dog : public Animal { + public: + std::string go(int n_times) override { + std::string result; + for (int i=0; i class PyAnimal : public AnimalBase { + public: + using AnimalBase::AnimalBase; // Inherit constructors + std::string go(int n_times) override { PYBIND11_OVERRIDE_PURE(std::string, AnimalBase, go, n_times); } + std::string name() override { PYBIND11_OVERRIDE(std::string, AnimalBase, name, ); } + }; + template class PyDog : public PyAnimal { + public: + using PyAnimal::PyAnimal; // Inherit constructors + // Override PyAnimal's pure virtual go() with a non-pure one: + std::string go(int n_times) override { PYBIND11_OVERRIDE(std::string, DogBase, go, n_times); } + std::string bark() override { PYBIND11_OVERRIDE(std::string, DogBase, bark, ); } + }; + +This technique has the advantage of requiring just one trampoline method to be +declared per virtual method and pure virtual method override. It does, +however, require the compiler to generate at least as many methods (and +possibly more, if both pure virtual and overridden pure virtual methods are +exposed, as above). + +The classes are then registered with pybind11 using: + +.. code-block:: cpp + + py::class_> animal(m, "Animal"); + py::class_> dog(m, "Dog"); + py::class_> husky(m, "Husky"); + // ... add animal, dog, husky definitions + +Note that ``Husky`` did not require a dedicated trampoline template class at +all, since it neither declares any new virtual methods nor provides any pure +virtual method implementations. + +With either the repeated-virtuals or templated trampoline methods in place, you +can now create a python class that inherits from ``Dog``: + +.. code-block:: python + + class ShihTzu(Dog): + def bark(self): + return "yip!" + +.. seealso:: + + See the file :file:`tests/test_virtual_functions.cpp` for complete examples + using both the duplication and templated trampoline approaches. + +.. _extended_aliases: + +Extended trampoline class functionality +======================================= + +.. _extended_class_functionality_forced_trampoline: + +Forced trampoline class initialisation +-------------------------------------- +The trampoline classes described in the previous sections are, by default, only +initialized when needed. More specifically, they are initialized when a python +class actually inherits from a registered type (instead of merely creating an +instance of the registered type), or when a registered constructor is only +valid for the trampoline class but not the registered class. This is primarily +for performance reasons: when the trampoline class is not needed for anything +except virtual method dispatching, not initializing the trampoline class +improves performance by avoiding needing to do a run-time check to see if the +inheriting python instance has an overridden method. + +Sometimes, however, it is useful to always initialize a trampoline class as an +intermediate class that does more than just handle virtual method dispatching. +For example, such a class might perform extra class initialization, extra +destruction operations, and might define new members and methods to enable a +more python-like interface to a class. + +In order to tell pybind11 that it should *always* initialize the trampoline +class when creating new instances of a type, the class constructors should be +declared using ``py::init_alias()`` instead of the usual +``py::init()``. This forces construction via the trampoline class, +ensuring member initialization and (eventual) destruction. + +.. seealso:: + + See the file :file:`tests/test_virtual_functions.cpp` for complete examples + showing both normal and forced trampoline instantiation. + +Different method signatures +--------------------------- +The macro's introduced in :ref:`overriding_virtuals` cover most of the standard +use cases when exposing C++ classes to Python. Sometimes it is hard or unwieldy +to create a direct one-on-one mapping between the arguments and method return +type. + +An example would be when the C++ signature contains output arguments using +references (See also :ref:`faq_reference_arguments`). Another way of solving +this is to use the method body of the trampoline class to do conversions to the +input and return of the Python method. + +The main building block to do so is the :func:`get_override`, this function +allows retrieving a method implemented in Python from within the trampoline's +methods. Consider for example a C++ method which has the signature +``bool myMethod(int32_t& value)``, where the return indicates whether +something should be done with the ``value``. This can be made convenient on the +Python side by allowing the Python function to return ``None`` or an ``int``: + +.. code-block:: cpp + + bool MyClass::myMethod(int32_t& value) + { + pybind11::gil_scoped_acquire gil; // Acquire the GIL while in this scope. + // Try to look up the overridden method on the Python side. + pybind11::function override = pybind11::get_override(this, "myMethod"); + if (override) { // method is found + auto obj = override(value); // Call the Python function. + if (py::isinstance(obj)) { // check if it returned a Python integer type + value = obj.cast(); // Cast it and assign it to the value. + return true; // Return true; value should be used. + } else { + return false; // Python returned none, return false. + } + } + return false; // Alternatively return MyClass::myMethod(value); + } + + +.. _custom_constructors: + +Custom constructors +=================== + +The syntax for binding constructors was previously introduced, but it only +works when a constructor of the appropriate arguments actually exists on the +C++ side. To extend this to more general cases, pybind11 makes it possible +to bind factory functions as constructors. For example, suppose you have a +class like this: + +.. code-block:: cpp + + class Example { + private: + Example(int); // private constructor + public: + // Factory function: + static Example create(int a) { return Example(a); } + }; + + py::class_(m, "Example") + .def(py::init(&Example::create)); + +While it is possible to create a straightforward binding of the static +``create`` method, it may sometimes be preferable to expose it as a constructor +on the Python side. This can be accomplished by calling ``.def(py::init(...))`` +with the function reference returning the new instance passed as an argument. +It is also possible to use this approach to bind a function returning a new +instance by raw pointer or by the holder (e.g. ``std::unique_ptr``). + +The following example shows the different approaches: + +.. code-block:: cpp + + class Example { + private: + Example(int); // private constructor + public: + // Factory function - returned by value: + static Example create(int a) { return Example(a); } + + // These constructors are publicly callable: + Example(double); + Example(int, int); + Example(std::string); + }; + + py::class_(m, "Example") + // Bind the factory function as a constructor: + .def(py::init(&Example::create)) + // Bind a lambda function returning a pointer wrapped in a holder: + .def(py::init([](std::string arg) { + return std::unique_ptr(new Example(arg)); + })) + // Return a raw pointer: + .def(py::init([](int a, int b) { return new Example(a, b); })) + // You can mix the above with regular C++ constructor bindings as well: + .def(py::init()) + ; + +When the constructor is invoked from Python, pybind11 will call the factory +function and store the resulting C++ instance in the Python instance. + +When combining factory functions constructors with :ref:`virtual function +trampolines ` there are two approaches. The first is to +add a constructor to the alias class that takes a base value by +rvalue-reference. If such a constructor is available, it will be used to +construct an alias instance from the value returned by the factory function. +The second option is to provide two factory functions to ``py::init()``: the +first will be invoked when no alias class is required (i.e. when the class is +being used but not inherited from in Python), and the second will be invoked +when an alias is required. + +You can also specify a single factory function that always returns an alias +instance: this will result in behaviour similar to ``py::init_alias<...>()``, +as described in the :ref:`extended trampoline class documentation +`. + +The following example shows the different factory approaches for a class with +an alias: + +.. code-block:: cpp + + #include + class Example { + public: + // ... + virtual ~Example() = default; + }; + class PyExample : public Example { + public: + using Example::Example; + PyExample(Example &&base) : Example(std::move(base)) {} + }; + py::class_(m, "Example") + // Returns an Example pointer. If a PyExample is needed, the Example + // instance will be moved via the extra constructor in PyExample, above. + .def(py::init([]() { return new Example(); })) + // Two callbacks: + .def(py::init([]() { return new Example(); } /* no alias needed */, + []() { return new PyExample(); } /* alias needed */)) + // *Always* returns an alias instance (like py::init_alias<>()) + .def(py::init([]() { return new PyExample(); })) + ; + +Brace initialization +-------------------- + +``pybind11::init<>`` internally uses C++11 brace initialization to call the +constructor of the target class. This means that it can be used to bind +*implicit* constructors as well: + +.. code-block:: cpp + + struct Aggregate { + int a; + std::string b; + }; + + py::class_(m, "Aggregate") + .def(py::init()); + +.. note:: + + Note that brace initialization preferentially invokes constructor overloads + taking a ``std::initializer_list``. In the rare event that this causes an + issue, you can work around it by using ``py::init(...)`` with a lambda + function that constructs the new object as desired. + +.. _classes_with_non_public_destructors: + +Non-public destructors +====================== + +If a class has a private or protected destructor (as might e.g. be the case in +a singleton pattern), a compile error will occur when creating bindings via +pybind11. The underlying issue is that the ``std::unique_ptr`` holder type that +is responsible for managing the lifetime of instances will reference the +destructor even if no deallocations ever take place. In order to expose classes +with private or protected destructors, it is possible to override the holder +type via a holder type argument to ``class_``. Pybind11 provides a helper class +``py::nodelete`` that disables any destructor invocations. In this case, it is +crucial that instances are deallocated on the C++ side to avoid memory leaks. + +.. code-block:: cpp + + /* ... definition ... */ + + class MyClass { + private: + ~MyClass() { } + }; + + /* ... binding code ... */ + + py::class_>(m, "MyClass") + .def(py::init<>()) + +.. _destructors_that_call_python: + +Destructors that call Python +============================ + +If a Python function is invoked from a C++ destructor, an exception may be thrown +of type :class:`error_already_set`. If this error is thrown out of a class destructor, +``std::terminate()`` will be called, terminating the process. Class destructors +must catch all exceptions of type :class:`error_already_set` to discard the Python +exception using :func:`error_already_set::discard_as_unraisable`. + +Every Python function should be treated as *possibly throwing*. When a Python generator +stops yielding items, Python will throw a ``StopIteration`` exception, which can pass +though C++ destructors if the generator's stack frame holds the last reference to C++ +objects. + +For more information, see :ref:`the documentation on exceptions `. + +.. code-block:: cpp + + class MyClass { + public: + ~MyClass() { + try { + py::print("Even printing is dangerous in a destructor"); + py::exec("raise ValueError('This is an unraisable exception')"); + } catch (py::error_already_set &e) { + // error_context should be information about where/why the occurred, + // e.g. use __func__ to get the name of the current function + e.discard_as_unraisable(__func__); + } + } + }; + +.. note:: + + pybind11 does not support C++ destructors marked ``noexcept(false)``. + +.. versionadded:: 2.6 + +.. _implicit_conversions: + +Implicit conversions +==================== + +Suppose that instances of two types ``A`` and ``B`` are used in a project, and +that an ``A`` can easily be converted into an instance of type ``B`` (examples of this +could be a fixed and an arbitrary precision number type). + +.. code-block:: cpp + + py::class_(m, "A") + /// ... members ... + + py::class_(m, "B") + .def(py::init()) + /// ... members ... + + m.def("func", + [](const B &) { /* .... */ } + ); + +To invoke the function ``func`` using a variable ``a`` containing an ``A`` +instance, we'd have to write ``func(B(a))`` in Python. On the other hand, C++ +will automatically apply an implicit type conversion, which makes it possible +to directly write ``func(a)``. + +In this situation (i.e. where ``B`` has a constructor that converts from +``A``), the following statement enables similar implicit conversions on the +Python side: + +.. code-block:: cpp + + py::implicitly_convertible(); + +.. note:: + + Implicit conversions from ``A`` to ``B`` only work when ``B`` is a custom + data type that is exposed to Python via pybind11. + + To prevent runaway recursion, implicit conversions are non-reentrant: an + implicit conversion invoked as part of another implicit conversion of the + same type (i.e. from ``A`` to ``B``) will fail. + +.. _static_properties: + +Static properties +================= + +The section on :ref:`properties` discussed the creation of instance properties +that are implemented in terms of C++ getters and setters. + +Static properties can also be created in a similar way to expose getters and +setters of static class attributes. Note that the implicit ``self`` argument +also exists in this case and is used to pass the Python ``type`` subclass +instance. This parameter will often not be needed by the C++ side, and the +following example illustrates how to instantiate a lambda getter function +that ignores it: + +.. code-block:: cpp + + py::class_(m, "Foo") + .def_property_readonly_static("foo", [](py::object /* self */) { return Foo(); }); + +Operator overloading +==================== + +Suppose that we're given the following ``Vector2`` class with a vector addition +and scalar multiplication operation, all implemented using overloaded operators +in C++. + +.. code-block:: cpp + + class Vector2 { + public: + Vector2(float x, float y) : x(x), y(y) { } + + Vector2 operator+(const Vector2 &v) const { return Vector2(x + v.x, y + v.y); } + Vector2 operator*(float value) const { return Vector2(x * value, y * value); } + Vector2& operator+=(const Vector2 &v) { x += v.x; y += v.y; return *this; } + Vector2& operator*=(float v) { x *= v; y *= v; return *this; } + + friend Vector2 operator*(float f, const Vector2 &v) { + return Vector2(f * v.x, f * v.y); + } + + std::string toString() const { + return "[" + std::to_string(x) + ", " + std::to_string(y) + "]"; + } + private: + float x, y; + }; + +The following snippet shows how the above operators can be conveniently exposed +to Python. + +.. code-block:: cpp + + #include + + PYBIND11_MODULE(example, m) { + py::class_(m, "Vector2") + .def(py::init()) + .def(py::self + py::self) + .def(py::self += py::self) + .def(py::self *= float()) + .def(float() * py::self) + .def(py::self * float()) + .def(-py::self) + .def("__repr__", &Vector2::toString); + } + +Note that a line like + +.. code-block:: cpp + + .def(py::self * float()) + +is really just short hand notation for + +.. code-block:: cpp + + .def("__mul__", [](const Vector2 &a, float b) { + return a * b; + }, py::is_operator()) + +This can be useful for exposing additional operators that don't exist on the +C++ side, or to perform other types of customization. The ``py::is_operator`` +flag marker is needed to inform pybind11 that this is an operator, which +returns ``NotImplemented`` when invoked with incompatible arguments rather than +throwing a type error. + +.. note:: + + To use the more convenient ``py::self`` notation, the additional + header file :file:`pybind11/operators.h` must be included. + +.. seealso:: + + The file :file:`tests/test_operator_overloading.cpp` contains a + complete example that demonstrates how to work with overloaded operators in + more detail. + +.. _pickling: + +Pickling support +================ + +Python's ``pickle`` module provides a powerful facility to serialize and +de-serialize a Python object graph into a binary data stream. To pickle and +unpickle C++ classes using pybind11, a ``py::pickle()`` definition must be +provided. Suppose the class in question has the following signature: + +.. code-block:: cpp + + class Pickleable { + public: + Pickleable(const std::string &value) : m_value(value) { } + const std::string &value() const { return m_value; } + + void setExtra(int extra) { m_extra = extra; } + int extra() const { return m_extra; } + private: + std::string m_value; + int m_extra = 0; + }; + +Pickling support in Python is enabled by defining the ``__setstate__`` and +``__getstate__`` methods [#f3]_. For pybind11 classes, use ``py::pickle()`` +to bind these two functions: + +.. code-block:: cpp + + py::class_(m, "Pickleable") + .def(py::init()) + .def("value", &Pickleable::value) + .def("extra", &Pickleable::extra) + .def("setExtra", &Pickleable::setExtra) + .def(py::pickle( + [](const Pickleable &p) { // __getstate__ + /* Return a tuple that fully encodes the state of the object */ + return py::make_tuple(p.value(), p.extra()); + }, + [](py::tuple t) { // __setstate__ + if (t.size() != 2) + throw std::runtime_error("Invalid state!"); + + /* Create a new C++ instance */ + Pickleable p(t[0].cast()); + + /* Assign any additional state */ + p.setExtra(t[1].cast()); + + return p; + } + )); + +The ``__setstate__`` part of the ``py::pickle()`` definition follows the same +rules as the single-argument version of ``py::init()``. The return type can be +a value, pointer or holder type. See :ref:`custom_constructors` for details. + +An instance can now be pickled as follows: + +.. code-block:: python + + import pickle + + p = Pickleable("test_value") + p.setExtra(15) + data = pickle.dumps(p) + + +.. note:: + If given, the second argument to ``dumps`` must be 2 or larger - 0 and 1 are + not supported. Newer versions are also fine; for instance, specify ``-1`` to + always use the latest available version. Beware: failure to follow these + instructions will cause important pybind11 memory allocation routines to be + skipped during unpickling, which will likely lead to memory corruption + and/or segmentation faults. Python defaults to version 3 (Python 3-3.7) and + version 4 for Python 3.8+. + +.. seealso:: + + The file :file:`tests/test_pickling.cpp` contains a complete example + that demonstrates how to pickle and unpickle types using pybind11 in more + detail. + +.. [#f3] http://docs.python.org/3/library/pickle.html#pickling-class-instances + +Deepcopy support +================ + +Python normally uses references in assignments. Sometimes a real copy is needed +to prevent changing all copies. The ``copy`` module [#f5]_ provides these +capabilities. + +A class with pickle support is automatically also (deep)copy +compatible. However, performance can be improved by adding custom +``__copy__`` and ``__deepcopy__`` methods. + +For simple classes (deep)copy can be enabled by using the copy constructor, +which should look as follows: + +.. code-block:: cpp + + py::class_(m, "Copyable") + .def("__copy__", [](const Copyable &self) { + return Copyable(self); + }) + .def("__deepcopy__", [](const Copyable &self, py::dict) { + return Copyable(self); + }, "memo"_a); + +.. note:: + + Dynamic attributes will not be copied in this example. + +.. [#f5] https://docs.python.org/3/library/copy.html + +Multiple Inheritance +==================== + +pybind11 can create bindings for types that derive from multiple base types +(aka. *multiple inheritance*). To do so, specify all bases in the template +arguments of the ``class_`` declaration: + +.. code-block:: cpp + + py::class_(m, "MyType") + ... + +The base types can be specified in arbitrary order, and they can even be +interspersed with alias types and holder types (discussed earlier in this +document)---pybind11 will automatically find out which is which. The only +requirement is that the first template argument is the type to be declared. + +It is also permitted to inherit multiply from exported C++ classes in Python, +as well as inheriting from multiple Python and/or pybind11-exported classes. + +There is one caveat regarding the implementation of this feature: + +When only one base type is specified for a C++ type that actually has multiple +bases, pybind11 will assume that it does not participate in multiple +inheritance, which can lead to undefined behavior. In such cases, add the tag +``multiple_inheritance`` to the class constructor: + +.. code-block:: cpp + + py::class_(m, "MyType", py::multiple_inheritance()); + +The tag is redundant and does not need to be specified when multiple base types +are listed. + +.. _module_local: + +Module-local class bindings +=========================== + +When creating a binding for a class, pybind11 by default makes that binding +"global" across modules. What this means is that a type defined in one module +can be returned from any module resulting in the same Python type. For +example, this allows the following: + +.. code-block:: cpp + + // In the module1.cpp binding code for module1: + py::class_(m, "Pet") + .def(py::init()) + .def_readonly("name", &Pet::name); + +.. code-block:: cpp + + // In the module2.cpp binding code for module2: + m.def("create_pet", [](std::string name) { return new Pet(name); }); + +.. code-block:: pycon + + >>> from module1 import Pet + >>> from module2 import create_pet + >>> pet1 = Pet("Kitty") + >>> pet2 = create_pet("Doggy") + >>> pet2.name() + 'Doggy' + +When writing binding code for a library, this is usually desirable: this +allows, for example, splitting up a complex library into multiple Python +modules. + +In some cases, however, this can cause conflicts. For example, suppose two +unrelated modules make use of an external C++ library and each provide custom +bindings for one of that library's classes. This will result in an error when +a Python program attempts to import both modules (directly or indirectly) +because of conflicting definitions on the external type: + +.. code-block:: cpp + + // dogs.cpp + + // Binding for external library class: + py::class(m, "Pet") + .def("name", &pets::Pet::name); + + // Binding for local extension class: + py::class(m, "Dog") + .def(py::init()); + +.. code-block:: cpp + + // cats.cpp, in a completely separate project from the above dogs.cpp. + + // Binding for external library class: + py::class(m, "Pet") + .def("get_name", &pets::Pet::name); + + // Binding for local extending class: + py::class(m, "Cat") + .def(py::init()); + +.. code-block:: pycon + + >>> import cats + >>> import dogs + Traceback (most recent call last): + File "", line 1, in + ImportError: generic_type: type "Pet" is already registered! + +To get around this, you can tell pybind11 to keep the external class binding +localized to the module by passing the ``py::module_local()`` attribute into +the ``py::class_`` constructor: + +.. code-block:: cpp + + // Pet binding in dogs.cpp: + py::class(m, "Pet", py::module_local()) + .def("name", &pets::Pet::name); + +.. code-block:: cpp + + // Pet binding in cats.cpp: + py::class(m, "Pet", py::module_local()) + .def("get_name", &pets::Pet::name); + +This makes the Python-side ``dogs.Pet`` and ``cats.Pet`` into distinct classes, +avoiding the conflict and allowing both modules to be loaded. C++ code in the +``dogs`` module that casts or returns a ``Pet`` instance will result in a +``dogs.Pet`` Python instance, while C++ code in the ``cats`` module will result +in a ``cats.Pet`` Python instance. + +This does come with two caveats, however: First, external modules cannot return +or cast a ``Pet`` instance to Python (unless they also provide their own local +bindings). Second, from the Python point of view they are two distinct classes. + +Note that the locality only applies in the C++ -> Python direction. When +passing such a ``py::module_local`` type into a C++ function, the module-local +classes are still considered. This means that if the following function is +added to any module (including but not limited to the ``cats`` and ``dogs`` +modules above) it will be callable with either a ``dogs.Pet`` or ``cats.Pet`` +argument: + +.. code-block:: cpp + + m.def("pet_name", [](const pets::Pet &pet) { return pet.name(); }); + +For example, suppose the above function is added to each of ``cats.cpp``, +``dogs.cpp`` and ``frogs.cpp`` (where ``frogs.cpp`` is some other module that +does *not* bind ``Pets`` at all). + +.. code-block:: pycon + + >>> import cats, dogs, frogs # No error because of the added py::module_local() + >>> mycat, mydog = cats.Cat("Fluffy"), dogs.Dog("Rover") + >>> (cats.pet_name(mycat), dogs.pet_name(mydog)) + ('Fluffy', 'Rover') + >>> (cats.pet_name(mydog), dogs.pet_name(mycat), frogs.pet_name(mycat)) + ('Rover', 'Fluffy', 'Fluffy') + +It is possible to use ``py::module_local()`` registrations in one module even +if another module registers the same type globally: within the module with the +module-local definition, all C++ instances will be cast to the associated bound +Python type. In other modules any such values are converted to the global +Python type created elsewhere. + +.. note:: + + STL bindings (as provided via the optional :file:`pybind11/stl_bind.h` + header) apply ``py::module_local`` by default when the bound type might + conflict with other modules; see :ref:`stl_bind` for details. + +.. note:: + + The localization of the bound types is actually tied to the shared object + or binary generated by the compiler/linker. For typical modules created + with ``PYBIND11_MODULE()``, this distinction is not significant. It is + possible, however, when :ref:`embedding` to embed multiple modules in the + same binary (see :ref:`embedding_modules`). In such a case, the + localization will apply across all embedded modules within the same binary. + +.. seealso:: + + The file :file:`tests/test_local_bindings.cpp` contains additional examples + that demonstrate how ``py::module_local()`` works. + +Binding protected member functions +================================== + +It's normally not possible to expose ``protected`` member functions to Python: + +.. code-block:: cpp + + class A { + protected: + int foo() const { return 42; } + }; + + py::class_(m, "A") + .def("foo", &A::foo); // error: 'foo' is a protected member of 'A' + +On one hand, this is good because non-``public`` members aren't meant to be +accessed from the outside. But we may want to make use of ``protected`` +functions in derived Python classes. + +The following pattern makes this possible: + +.. code-block:: cpp + + class A { + protected: + int foo() const { return 42; } + }; + + class Publicist : public A { // helper type for exposing protected functions + public: + using A::foo; // inherited with different access modifier + }; + + py::class_(m, "A") // bind the primary class + .def("foo", &Publicist::foo); // expose protected methods via the publicist + +This works because ``&Publicist::foo`` is exactly the same function as +``&A::foo`` (same signature and address), just with a different access +modifier. The only purpose of the ``Publicist`` helper class is to make +the function name ``public``. + +If the intent is to expose ``protected`` ``virtual`` functions which can be +overridden in Python, the publicist pattern can be combined with the previously +described trampoline: + +.. code-block:: cpp + + class A { + public: + virtual ~A() = default; + + protected: + virtual int foo() const { return 42; } + }; + + class Trampoline : public A { + public: + int foo() const override { PYBIND11_OVERRIDE(int, A, foo, ); } + }; + + class Publicist : public A { + public: + using A::foo; + }; + + py::class_(m, "A") // <-- `Trampoline` here + .def("foo", &Publicist::foo); // <-- `Publicist` here, not `Trampoline`! + +Binding final classes +===================== + +Some classes may not be appropriate to inherit from. In C++11, classes can +use the ``final`` specifier to ensure that a class cannot be inherited from. +The ``py::is_final`` attribute can be used to ensure that Python classes +cannot inherit from a specified type. The underlying C++ type does not need +to be declared final. + +.. code-block:: cpp + + class IsFinal final {}; + + py::class_(m, "IsFinal", py::is_final()); + +When you try to inherit from such a class in Python, you will now get this +error: + +.. code-block:: pycon + + >>> class PyFinalChild(IsFinal): + ... pass + ... + TypeError: type 'IsFinal' is not an acceptable base type + +.. note:: This attribute is currently ignored on PyPy + +.. versionadded:: 2.6 + +Binding classes with template parameters +======================================== + +pybind11 can also wrap classes that have template parameters. Consider these classes: + +.. code-block:: cpp + + struct Cat {}; + struct Dog {}; + + template + struct Cage { + Cage(PetType& pet); + PetType& get(); + }; + +C++ templates may only be instantiated at compile time, so pybind11 can only +wrap instantiated templated classes. You cannot wrap a non-instantiated template: + +.. code-block:: cpp + + // BROKEN (this will not compile) + py::class_(m, "Cage"); + .def("get", &Cage::get); + +You must explicitly specify each template/type combination that you want to +wrap separately. + +.. code-block:: cpp + + // ok + py::class_>(m, "CatCage") + .def("get", &Cage::get); + + // ok + py::class_>(m, "DogCage") + .def("get", &Cage::get); + +If your class methods have template parameters you can wrap those as well, +but once again each instantiation must be explicitly specified: + +.. code-block:: cpp + + typename + struct MyClass { + template + T fn(V v); + }; + + py::class>(m, "MyClassT") + .def("fn", &MyClass::fn); + +Custom automatic downcasters +============================ + +As explained in :ref:`inheritance`, pybind11 comes with built-in +understanding of the dynamic type of polymorphic objects in C++; that +is, returning a Pet to Python produces a Python object that knows it's +wrapping a Dog, if Pet has virtual methods and pybind11 knows about +Dog and this Pet is in fact a Dog. Sometimes, you might want to +provide this automatic downcasting behavior when creating bindings for +a class hierarchy that does not use standard C++ polymorphism, such as +LLVM [#f4]_. As long as there's some way to determine at runtime +whether a downcast is safe, you can proceed by specializing the +``pybind11::polymorphic_type_hook`` template: + +.. code-block:: cpp + + enum class PetKind { Cat, Dog, Zebra }; + struct Pet { // Not polymorphic: has no virtual methods + const PetKind kind; + int age = 0; + protected: + Pet(PetKind _kind) : kind(_kind) {} + }; + struct Dog : Pet { + Dog() : Pet(PetKind::Dog) {} + std::string sound = "woof!"; + std::string bark() const { return sound; } + }; + + 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 + if (src && src->kind == PetKind::Dog) { + type = &typeid(Dog); + return static_cast(src); + } + return src; + } + }; + } // 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 +determine if a downcast is possible. The ``get()`` function should use +whatever runtime information is available to determine if its ``src`` +parameter is in fact an instance of some class ``Derived`` that +inherits from ``Base``. If it finds such a ``Derived``, it sets ``type += &typeid(Derived)`` and returns a pointer to the ``Derived`` object +that contains ``src``. Otherwise, it just returns ``src``, leaving +``type`` at its default value of nullptr. If you set ``type`` to a +type that pybind11 doesn't know about, no downcasting will occur, and +the original ``src`` pointer will be used with its static type +``Base*``. + +It is critical that the returned pointer and ``type`` argument of +``get()`` agree with each other: if ``type`` is set to something +non-null, the returned pointer must point to the start of an object +whose type is ``type``. If the hierarchy being exposed uses only +single inheritance, a simple ``return src;`` will achieve this just +fine, but in the general case, you must cast ``src`` to the +appropriate derived-class pointer (e.g. using +``static_cast(src)``) before allowing it to be returned as a +``void*``. + +.. [#f4] https://llvm.org/docs/HowToSetUpLLVMStyleRTTI.html + +.. note:: + + pybind11's standard support for downcasting objects whose types + have virtual methods is implemented using + ``polymorphic_type_hook`` too, using the standard C++ ability to + determine the most-derived type of a polymorphic object using + ``typeid()`` and to cast a base pointer to that most-derived type + (even if you don't know what it is) using ``dynamic_cast``. + +.. seealso:: + + The file :file:`tests/test_tagbased_polymorphic.cpp` contains a + more complete example, including a demonstration of how to provide + automatic downcasting for an entire class hierarchy without + writing one get() function for each class. + +Accessing the type object +========================= + +You can get the type object from a C++ class that has already been registered using: + +.. code-block:: cpp + + py::type T_py = py::type::of(); + +You can directly use ``py::type::of(ob)`` to get the type object from any python +object, just like ``type(ob)`` in Python. + +.. note:: + + Other types, like ``py::type::of()``, do not work, see :ref:`type-conversions`. + +.. versionadded:: 2.6 + +Custom type setup +================= + +For advanced use cases, such as enabling garbage collection support, you may +wish to directly manipulate the ``PyHeapTypeObject`` corresponding to a +``py::class_`` definition. + +You can do that using ``py::custom_type_setup``: + +.. code-block:: cpp + + struct OwnsPythonObjects { + py::object value = py::none(); + }; + py::class_ cls( + m, "OwnsPythonObjects", py::custom_type_setup([](PyHeapTypeObject *heap_type) { + auto *type = &heap_type->ht_type; + type->tp_flags |= Py_TPFLAGS_HAVE_GC; + type->tp_traverse = [](PyObject *self_base, visitproc visit, void *arg) { + auto &self = py::cast(py::handle(self_base)); + Py_VISIT(self.value.ptr()); + return 0; + }; + type->tp_clear = [](PyObject *self_base) { + auto &self = py::cast(py::handle(self_base)); + self.value = py::none(); + return 0; + }; + })); + cls.def(py::init<>()); + cls.def_readwrite("value", &OwnsPythonObjects::value); + +.. versionadded:: 2.8 diff --git a/extern/pybind11/docs/advanced/embedding.rst b/extern/pybind11/docs/advanced/embedding.rst new file mode 100644 index 000000000..4cb6ebc68 --- /dev/null +++ b/extern/pybind11/docs/advanced/embedding.rst @@ -0,0 +1,262 @@ +.. _embedding: + +Embedding the interpreter +######################### + +While pybind11 is mainly focused on extending Python using C++, it's also +possible to do the reverse: embed the Python interpreter into a C++ program. +All of the other documentation pages still apply here, so refer to them for +general pybind11 usage. This section will cover a few extra things required +for embedding. + +Getting started +=============== + +A basic executable with an embedded interpreter can be created with just a few +lines of CMake and the ``pybind11::embed`` target, as shown below. For more +information, see :doc:`/compiling`. + +.. code-block:: cmake + + cmake_minimum_required(VERSION 3.5...3.27) + project(example) + + find_package(pybind11 REQUIRED) # or `add_subdirectory(pybind11)` + + add_executable(example main.cpp) + target_link_libraries(example PRIVATE pybind11::embed) + +The essential structure of the ``main.cpp`` file looks like this: + +.. code-block:: cpp + + #include // everything needed for embedding + namespace py = pybind11; + + int main() { + py::scoped_interpreter guard{}; // start the interpreter and keep it alive + + py::print("Hello, World!"); // use the Python API + } + +The interpreter must be initialized before using any Python API, which includes +all the functions and classes in pybind11. The RAII guard class ``scoped_interpreter`` +takes care of the interpreter lifetime. After the guard is destroyed, the interpreter +shuts down and clears its memory. No Python functions can be called after this. + +Executing Python code +===================== + +There are a few different ways to run Python code. One option is to use ``eval``, +``exec`` or ``eval_file``, as explained in :ref:`eval`. Here is a quick example in +the context of an executable with an embedded interpreter: + +.. code-block:: cpp + + #include + namespace py = pybind11; + + int main() { + py::scoped_interpreter guard{}; + + py::exec(R"( + kwargs = dict(name="World", number=42) + message = "Hello, {name}! The answer is {number}".format(**kwargs) + print(message) + )"); + } + +Alternatively, similar results can be achieved using pybind11's API (see +:doc:`/advanced/pycpp/index` for more details). + +.. code-block:: cpp + + #include + namespace py = pybind11; + using namespace py::literals; + + int main() { + py::scoped_interpreter guard{}; + + auto kwargs = py::dict("name"_a="World", "number"_a=42); + auto message = "Hello, {name}! The answer is {number}"_s.format(**kwargs); + py::print(message); + } + +The two approaches can also be combined: + +.. code-block:: cpp + + #include + #include + + namespace py = pybind11; + using namespace py::literals; + + int main() { + py::scoped_interpreter guard{}; + + auto locals = py::dict("name"_a="World", "number"_a=42); + py::exec(R"( + message = "Hello, {name}! The answer is {number}".format(**locals()) + )", py::globals(), locals); + + auto message = locals["message"].cast(); + std::cout << message; + } + +Importing modules +================= + +Python modules can be imported using ``module_::import()``: + +.. code-block:: cpp + + py::module_ sys = py::module_::import("sys"); + py::print(sys.attr("path")); + +For convenience, the current working directory is included in ``sys.path`` when +embedding the interpreter. This makes it easy to import local Python files: + +.. code-block:: python + + """calc.py located in the working directory""" + + + def add(i, j): + return i + j + + +.. code-block:: cpp + + py::module_ calc = py::module_::import("calc"); + py::object result = calc.attr("add")(1, 2); + int n = result.cast(); + assert(n == 3); + +Modules can be reloaded using ``module_::reload()`` if the source is modified e.g. +by an external process. This can be useful in scenarios where the application +imports a user defined data processing script which needs to be updated after +changes by the user. Note that this function does not reload modules recursively. + +.. _embedding_modules: + +Adding embedded modules +======================= + +Embedded binary modules can be added using the ``PYBIND11_EMBEDDED_MODULE`` macro. +Note that the definition must be placed at global scope. They can be imported +like any other module. + +.. code-block:: cpp + + #include + namespace py = pybind11; + + PYBIND11_EMBEDDED_MODULE(fast_calc, m) { + // `m` is a `py::module_` which is used to bind functions and classes + m.def("add", [](int i, int j) { + return i + j; + }); + } + + int main() { + py::scoped_interpreter guard{}; + + auto fast_calc = py::module_::import("fast_calc"); + auto result = fast_calc.attr("add")(1, 2).cast(); + assert(result == 3); + } + +Unlike extension modules where only a single binary module can be created, on +the embedded side an unlimited number of modules can be added using multiple +``PYBIND11_EMBEDDED_MODULE`` definitions (as long as they have unique names). + +These modules are added to Python's list of builtins, so they can also be +imported in pure Python files loaded by the interpreter. Everything interacts +naturally: + +.. code-block:: python + + """py_module.py located in the working directory""" + import cpp_module + + a = cpp_module.a + b = a + 1 + + +.. code-block:: cpp + + #include + namespace py = pybind11; + + PYBIND11_EMBEDDED_MODULE(cpp_module, m) { + m.attr("a") = 1; + } + + int main() { + py::scoped_interpreter guard{}; + + auto py_module = py::module_::import("py_module"); + + auto locals = py::dict("fmt"_a="{} + {} = {}", **py_module.attr("__dict__")); + assert(locals["a"].cast() == 1); + assert(locals["b"].cast() == 2); + + py::exec(R"( + c = a + b + message = fmt.format(a, b, c) + )", py::globals(), locals); + + assert(locals["c"].cast() == 3); + assert(locals["message"].cast() == "1 + 2 = 3"); + } + + +Interpreter lifetime +==================== + +The Python interpreter shuts down when ``scoped_interpreter`` is destroyed. After +this, creating a new instance will restart the interpreter. Alternatively, the +``initialize_interpreter`` / ``finalize_interpreter`` pair of functions can be used +to directly set the state at any time. + +Modules created with pybind11 can be safely re-initialized after the interpreter +has been restarted. However, this may not apply to third-party extension modules. +The issue is that Python itself cannot completely unload extension modules and +there are several caveats with regard to interpreter restarting. In short, not +all memory may be freed, either due to Python reference cycles or user-created +global data. All the details can be found in the CPython documentation. + +.. warning:: + + Creating two concurrent ``scoped_interpreter`` guards is a fatal error. So is + calling ``initialize_interpreter`` for a second time after the interpreter + has already been initialized. + + Do not use the raw CPython API functions ``Py_Initialize`` and + ``Py_Finalize`` as these do not properly handle the lifetime of + pybind11's internal data. + + +Sub-interpreter support +======================= + +Creating multiple copies of ``scoped_interpreter`` is not possible because it +represents the main Python interpreter. Sub-interpreters are something different +and they do permit the existence of multiple interpreters. This is an advanced +feature of the CPython API and should be handled with care. pybind11 does not +currently offer a C++ interface for sub-interpreters, so refer to the CPython +documentation for all the details regarding this feature. + +We'll just mention a couple of caveats the sub-interpreters support in pybind11: + + 1. Sub-interpreters will not receive independent copies of embedded modules. + Instead, these are shared and modifications in one interpreter may be + reflected in another. + + 2. Managing multiple threads, multiple interpreters and the GIL can be + challenging and there are several caveats here, even within the pure + CPython API (please refer to the Python docs for details). As for + pybind11, keep in mind that ``gil_scoped_release`` and ``gil_scoped_acquire`` + do not take sub-interpreters into account. diff --git a/extern/pybind11/docs/advanced/exceptions.rst b/extern/pybind11/docs/advanced/exceptions.rst new file mode 100644 index 000000000..e20f42b5f --- /dev/null +++ b/extern/pybind11/docs/advanced/exceptions.rst @@ -0,0 +1,401 @@ +Exceptions +########## + +Built-in C++ to Python exception translation +============================================ + +When Python calls C++ code through pybind11, pybind11 provides a C++ exception handler +that will trap C++ exceptions, translate them to the corresponding Python exception, +and raise them so that Python code can handle them. + +pybind11 defines translations for ``std::exception`` and its standard +subclasses, and several special exception classes that translate to specific +Python exceptions. Note that these are not actually Python exceptions, so they +cannot be examined using the Python C API. Instead, they are pure C++ objects +that pybind11 will translate the corresponding Python exception when they arrive +at its exception handler. + +.. tabularcolumns:: |p{0.5\textwidth}|p{0.45\textwidth}| + ++--------------------------------------+--------------------------------------+ +| Exception thrown by C++ | Translated to Python exception type | ++======================================+======================================+ +| :class:`std::exception` | ``RuntimeError`` | ++--------------------------------------+--------------------------------------+ +| :class:`std::bad_alloc` | ``MemoryError`` | ++--------------------------------------+--------------------------------------+ +| :class:`std::domain_error` | ``ValueError`` | ++--------------------------------------+--------------------------------------+ +| :class:`std::invalid_argument` | ``ValueError`` | ++--------------------------------------+--------------------------------------+ +| :class:`std::length_error` | ``ValueError`` | ++--------------------------------------+--------------------------------------+ +| :class:`std::out_of_range` | ``IndexError`` | ++--------------------------------------+--------------------------------------+ +| :class:`std::range_error` | ``ValueError`` | ++--------------------------------------+--------------------------------------+ +| :class:`std::overflow_error` | ``OverflowError`` | ++--------------------------------------+--------------------------------------+ +| :class:`pybind11::stop_iteration` | ``StopIteration`` (used to implement | +| | custom iterators) | ++--------------------------------------+--------------------------------------+ +| :class:`pybind11::index_error` | ``IndexError`` (used to indicate out | +| | of bounds access in ``__getitem__``, | +| | ``__setitem__``, etc.) | ++--------------------------------------+--------------------------------------+ +| :class:`pybind11::key_error` | ``KeyError`` (used to indicate out | +| | of bounds access in ``__getitem__``, | +| | ``__setitem__`` in dict-like | +| | objects, etc.) | ++--------------------------------------+--------------------------------------+ +| :class:`pybind11::value_error` | ``ValueError`` (used to indicate | +| | wrong value passed in | +| | ``container.remove(...)``) | ++--------------------------------------+--------------------------------------+ +| :class:`pybind11::type_error` | ``TypeError`` | ++--------------------------------------+--------------------------------------+ +| :class:`pybind11::buffer_error` | ``BufferError`` | ++--------------------------------------+--------------------------------------+ +| :class:`pybind11::import_error` | ``ImportError`` | ++--------------------------------------+--------------------------------------+ +| :class:`pybind11::attribute_error` | ``AttributeError`` | ++--------------------------------------+--------------------------------------+ +| Any other exception | ``RuntimeError`` | ++--------------------------------------+--------------------------------------+ + +Exception translation is not bidirectional. That is, *catching* the C++ +exceptions defined above will not trap exceptions that originate from +Python. For that, catch :class:`pybind11::error_already_set`. See :ref:`below +` for further details. + +There is also a special exception :class:`cast_error` that is thrown by +:func:`handle::call` when the input arguments cannot be converted to Python +objects. + +Registering custom translators +============================== + +If the default exception conversion policy described above is insufficient, +pybind11 also provides support for registering custom exception translators. +Similar to pybind11 classes, exception translators can be local to the module +they are defined in or global to the entire python session. To register a simple +exception conversion that translates a C++ exception into a new Python exception +using the C++ exception's ``what()`` method, a helper function is available: + +.. code-block:: cpp + + py::register_exception(module, "PyExp"); + +This call creates a Python exception class with the name ``PyExp`` in the given +module and automatically converts any encountered exceptions of type ``CppExp`` +into Python exceptions of type ``PyExp``. + +A matching function is available for registering a local exception translator: + +.. code-block:: cpp + + py::register_local_exception(module, "PyExp"); + + +It is possible to specify base class for the exception using the third +parameter, a ``handle``: + +.. code-block:: cpp + + py::register_exception(module, "PyExp", PyExc_RuntimeError); + py::register_local_exception(module, "PyExp", PyExc_RuntimeError); + +Then ``PyExp`` can be caught both as ``PyExp`` and ``RuntimeError``. + +The class objects of the built-in Python exceptions are listed in the Python +documentation on `Standard Exceptions `_. +The default base class is ``PyExc_Exception``. + +When more advanced exception translation is needed, the functions +``py::register_exception_translator(translator)`` and +``py::register_local_exception_translator(translator)`` can be used to register +functions that can translate arbitrary exception types (and which may include +additional logic to do so). The functions takes a stateless callable (e.g. a +function pointer or a lambda function without captured variables) with the call +signature ``void(std::exception_ptr)``. + +When a C++ exception is thrown, the registered exception translators are tried +in reverse order of registration (i.e. the last registered translator gets the +first shot at handling the exception). All local translators will be tried +before a global translator is tried. + +Inside the translator, ``std::rethrow_exception`` should be used within +a try block to re-throw the exception. One or more catch clauses to catch +the appropriate exceptions should then be used with each clause using +``py::set_error()`` (see below). + +To declare a custom Python exception type, declare a ``py::exception`` variable +and use this in the associated exception translator (note: it is often useful +to make this a static declaration when using it inside a lambda expression +without requiring capturing). + +The following example demonstrates this for a hypothetical exception classes +``MyCustomException`` and ``OtherException``: the first is translated to a +custom python exception ``MyCustomError``, while the second is translated to a +standard python RuntimeError: + +.. code-block:: cpp + + PYBIND11_CONSTINIT static py::gil_safe_call_once_and_store exc_storage; + exc_storage.call_once_and_store_result( + [&]() { return py::exception(m, "MyCustomError"); }); + py::register_exception_translator([](std::exception_ptr p) { + try { + if (p) std::rethrow_exception(p); + } catch (const MyCustomException &e) { + py::set_error(exc_storage.get_stored(), e.what()); + } catch (const OtherException &e) { + py::set_error(PyExc_RuntimeError, e.what()); + } + }); + +Multiple exceptions can be handled by a single translator, as shown in the +example above. If the exception is not caught by the current translator, the +previously registered one gets a chance. + +If none of the registered exception translators is able to handle the +exception, it is handled by the default converter as described in the previous +section. + +.. seealso:: + + The file :file:`tests/test_exceptions.cpp` contains examples + of various custom exception translators and custom exception types. + +.. note:: + + Call ``py::set_error()`` for every exception caught in a custom exception + translator. Failure to do so will cause Python to crash with ``SystemError: + error return without exception set``. + + Exceptions that you do not plan to handle should simply not be caught, or + may be explicitly (re-)thrown to delegate it to the other, + previously-declared existing exception translators. + + 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 +===================================== + +When a global exception translator is registered, it will be applied across all +modules in the reverse order of registration. This can create behavior where the +order of module import influences how exceptions are translated. + +If module1 has the following translator: + +.. code-block:: cpp + + py::register_exception_translator([](std::exception_ptr p) { + try { + if (p) std::rethrow_exception(p); + } catch (const std::invalid_argument &e) { + py::set_error(PyExc_ArgumentError, "module1 handled this"); + } + } + +and module2 has the following similar translator: + +.. code-block:: cpp + + py::register_exception_translator([](std::exception_ptr p) { + try { + if (p) std::rethrow_exception(p); + } catch (const std::invalid_argument &e) { + py::set_error(PyExc_ArgumentError, "module2 handled this"); + } + } + +then which translator handles the invalid_argument will be determined by the +order that module1 and module2 are imported. Since exception translators are +applied in the reverse order of registration, which ever module was imported +last will "win" and that translator will be applied. + +If there are multiple pybind11 modules that share exception types (either +standard built-in or custom) loaded into a single python instance and +consistent error handling behavior is needed, then local translators should be +used. + +Changing the previous example to use ``register_local_exception_translator`` +would mean that when invalid_argument is thrown in the module2 code, the +module2 translator will always handle it, while in module1, the module1 +translator will do the same. + +.. _handling_python_exceptions_cpp: + +Handling exceptions from Python in C++ +====================================== + +When C++ calls Python functions, such as in a callback function or when +manipulating Python objects, and Python raises an ``Exception``, pybind11 +converts the Python exception into a C++ exception of type +:class:`pybind11::error_already_set` whose payload contains a C++ string textual +summary and the actual Python exception. ``error_already_set`` is used to +propagate Python exception back to Python (or possibly, handle them in C++). + +.. tabularcolumns:: |p{0.5\textwidth}|p{0.45\textwidth}| + ++--------------------------------------+--------------------------------------+ +| Exception raised in Python | Thrown as C++ exception type | ++======================================+======================================+ +| Any Python ``Exception`` | :class:`pybind11::error_already_set` | ++--------------------------------------+--------------------------------------+ + +For example: + +.. code-block:: cpp + + try { + // open("missing.txt", "r") + auto file = py::module_::import("io").attr("open")("missing.txt", "r"); + auto text = file.attr("read")(); + file.attr("close")(); + } catch (py::error_already_set &e) { + if (e.matches(PyExc_FileNotFoundError)) { + py::print("missing.txt not found"); + } else if (e.matches(PyExc_PermissionError)) { + py::print("missing.txt found but not accessible"); + } else { + throw; + } + } + +Note that C++ to Python exception translation does not apply here, since that is +a method for translating C++ exceptions to Python, not vice versa. The error raised +from Python is always ``error_already_set``. + +This example illustrates this behavior: + +.. code-block:: cpp + + try { + py::eval("raise ValueError('The Ring')"); + } catch (py::value_error &boromir) { + // Boromir never gets the ring + assert(false); + } catch (py::error_already_set &frodo) { + // Frodo gets the ring + py::print("I will take the ring"); + } + + try { + // py::value_error is a request for pybind11 to raise a Python exception + throw py::value_error("The ball"); + } catch (py::error_already_set &cat) { + // cat won't catch the ball since + // py::value_error is not a Python exception + assert(false); + } catch (py::value_error &dog) { + // dog will catch the ball + py::print("Run Spot run"); + throw; // Throw it again (pybind11 will raise ValueError) + } + +Handling errors from the Python C API +===================================== + +Where possible, use :ref:`pybind11 wrappers ` instead of calling +the Python C API directly. When calling the Python C API directly, in +addition to manually managing reference counts, one must follow the pybind11 +error protocol, which is outlined here. + +After calling the Python C API, if Python returns an error, +``throw py::error_already_set();``, which allows pybind11 to deal with the +exception and pass it back to the Python interpreter. This includes calls to +the error setting functions such as ``py::set_error()``. + +.. code-block:: cpp + + py::set_error(PyExc_TypeError, "C API type error demo"); + throw py::error_already_set(); + + // But it would be easier to simply... + throw py::type_error("pybind11 wrapper type error"); + +Alternately, to ignore the error, call `PyErr_Clear +`_. + +Any Python error must be thrown or cleared, or Python/pybind11 will be left in +an invalid state. + +Chaining exceptions ('raise from') +================================== + +Python has a mechanism for indicating that exceptions were caused by other +exceptions: + +.. code-block:: py + + try: + print(1 / 0) + except Exception as exc: + raise RuntimeError("could not divide by zero") from exc + +To do a similar thing in pybind11, you can use the ``py::raise_from`` function. It +sets the current python error indicator, so to continue propagating the exception +you should ``throw py::error_already_set()``. + +.. code-block:: cpp + + try { + py::eval("print(1 / 0")); + } catch (py::error_already_set &e) { + py::raise_from(e, PyExc_RuntimeError, "could not divide by zero"); + throw py::error_already_set(); + } + +.. versionadded:: 2.8 + +.. _unraisable_exceptions: + +Handling unraisable exceptions +============================== + +If a Python function invoked from a C++ destructor or any function marked +``noexcept(true)`` (collectively, "noexcept functions") throws an exception, there +is no way to propagate the exception, as such functions may not throw. +Should they throw or fail to catch any exceptions in their call graph, +the C++ runtime calls ``std::terminate()`` to abort immediately. + +Similarly, Python exceptions raised in a class's ``__del__`` method do not +propagate, but are logged by Python as an unraisable error. In Python 3.8+, a +`system hook is triggered +`_ +and an auditing event is logged. + +Any noexcept function should have a try-catch block that traps +class:`error_already_set` (or any other exception that can occur). Note that +pybind11 wrappers around Python exceptions such as +:class:`pybind11::value_error` are *not* Python exceptions; they are C++ +exceptions that pybind11 catches and converts to Python exceptions. Noexcept +functions cannot propagate these exceptions either. A useful approach is to +convert them to Python exceptions and then ``discard_as_unraisable`` as shown +below. + +.. code-block:: cpp + + void nonthrowing_func() noexcept(true) { + try { + // ... + } catch (py::error_already_set &eas) { + // Discard the Python error using Python APIs, using the C++ magic + // variable __func__. Python already knows the type and value and of the + // exception object. + eas.discard_as_unraisable(__func__); + } catch (const std::exception &e) { + // Log and discard C++ exceptions. + third_party::log(e); + } + } + +.. versionadded:: 2.6 diff --git a/extern/pybind11/docs/advanced/functions.rst b/extern/pybind11/docs/advanced/functions.rst new file mode 100644 index 000000000..372934b09 --- /dev/null +++ b/extern/pybind11/docs/advanced/functions.rst @@ -0,0 +1,614 @@ +Functions +######### + +Before proceeding with this section, make sure that you are already familiar +with the basics of binding functions and classes, as explained in :doc:`/basics` +and :doc:`/classes`. The following guide is applicable to both free and member +functions, i.e. *methods* in Python. + +.. _return_value_policies: + +Return value policies +===================== + +Python and C++ use fundamentally different ways of managing the memory and +lifetime of objects managed by them. This can lead to issues when creating +bindings for functions that return a non-trivial type. Just by looking at the +type information, it is not clear whether Python should take charge of the +returned value and eventually free its resources, or if this is handled on the +C++ side. For this reason, pybind11 provides several *return value policy* +annotations that can be passed to the :func:`module_::def` and +:func:`class_::def` functions. The default policy is +:enum:`return_value_policy::automatic`. + +Return value policies are tricky, and it's very important to get them right. +Just to illustrate what can go wrong, consider the following simple example: + +.. code-block:: cpp + + /* Function declaration */ + Data *get_data() { return _data; /* (pointer to a static data structure) */ } + ... + + /* Binding code */ + m.def("get_data", &get_data); // <-- KABOOM, will cause crash when called from Python + +What's going on here? When ``get_data()`` is called from Python, the return +value (a native C++ type) must be wrapped to turn it into a usable Python type. +In this case, the default return value policy (:enum:`return_value_policy::automatic`) +causes pybind11 to assume ownership of the static ``_data`` instance. + +When Python's garbage collector eventually deletes the Python +wrapper, pybind11 will also attempt to delete the C++ instance (via ``operator +delete()``) due to the implied ownership. At this point, the entire application +will come crashing down, though errors could also be more subtle and involve +silent data corruption. + +In the above example, the policy :enum:`return_value_policy::reference` should have +been specified so that the global data instance is only *referenced* without any +implied transfer of ownership, i.e.: + +.. code-block:: cpp + + m.def("get_data", &get_data, py::return_value_policy::reference); + +On the other hand, this is not the right policy for many other situations, +where ignoring ownership could lead to resource leaks. +As a developer using pybind11, it's important to be familiar with the different +return value policies, including which situation calls for which one of them. +The following table provides an overview of available policies: + +.. tabularcolumns:: |p{0.5\textwidth}|p{0.45\textwidth}| + ++--------------------------------------------------+----------------------------------------------------------------------------+ +| Return value policy | Description | ++==================================================+============================================================================+ +| :enum:`return_value_policy::take_ownership` | Reference an existing object (i.e. do not create a new copy) and take | +| | ownership. Python will call the destructor and delete operator when the | +| | object's reference count reaches zero. Undefined behavior ensues when the | +| | C++ side does the same, or when the data was not dynamically allocated. | ++--------------------------------------------------+----------------------------------------------------------------------------+ +| :enum:`return_value_policy::copy` | Create a new copy of the returned object, which will be owned by Python. | +| | This policy is comparably safe because the lifetimes of the two instances | +| | are decoupled. | ++--------------------------------------------------+----------------------------------------------------------------------------+ +| :enum:`return_value_policy::move` | Use ``std::move`` to move the return value contents into a new instance | +| | that will be owned by Python. This policy is comparably safe because the | +| | lifetimes of the two instances (move source and destination) are decoupled.| ++--------------------------------------------------+----------------------------------------------------------------------------+ +| :enum:`return_value_policy::reference` | Reference an existing object, but do not take ownership. The C++ side is | +| | responsible for managing the object's lifetime and deallocating it when | +| | it is no longer used. Warning: undefined behavior will ensue when the C++ | +| | side deletes an object that is still referenced and used by Python. | ++--------------------------------------------------+----------------------------------------------------------------------------+ +| :enum:`return_value_policy::reference_internal` | Indicates that the lifetime of the return value is tied to the lifetime | +| | of a parent object, namely the implicit ``this``, or ``self`` argument of | +| | the called method or property. Internally, this policy works just like | +| | :enum:`return_value_policy::reference` but additionally applies a | +| | ``keep_alive<0, 1>`` *call policy* (described in the next section) that | +| | prevents the parent object from being garbage collected as long as the | +| | return value is referenced by Python. This is the default policy for | +| | property getters created via ``def_property``, ``def_readwrite``, etc. | ++--------------------------------------------------+----------------------------------------------------------------------------+ +| :enum:`return_value_policy::automatic` | This policy falls back to the policy | +| | :enum:`return_value_policy::take_ownership` when the return value is a | +| | pointer. Otherwise, it uses :enum:`return_value_policy::move` or | +| | :enum:`return_value_policy::copy` for rvalue and lvalue references, | +| | respectively. See above for a description of what all of these different | +| | policies do. This is the default policy for ``py::class_``-wrapped types. | ++--------------------------------------------------+----------------------------------------------------------------------------+ +| :enum:`return_value_policy::automatic_reference` | As above, but use policy :enum:`return_value_policy::reference` when the | +| | return value is a pointer. This is the default conversion policy for | +| | function arguments when calling Python functions manually from C++ code | +| | (i.e. via ``handle::operator()``) and the casters in ``pybind11/stl.h``. | +| | You probably won't need to use this explicitly. | ++--------------------------------------------------+----------------------------------------------------------------------------+ + +Return value policies can also be applied to properties: + +.. code-block:: cpp + + class_(m, "MyClass") + .def_property("data", &MyClass::getData, &MyClass::setData, + py::return_value_policy::copy); + +Technically, the code above applies the policy to both the getter and the +setter function, however, the setter doesn't really care about *return* +value policies which makes this a convenient terse syntax. Alternatively, +targeted arguments can be passed through the :class:`cpp_function` constructor: + +.. code-block:: cpp + + class_(m, "MyClass") + .def_property("data", + py::cpp_function(&MyClass::getData, py::return_value_policy::copy), + py::cpp_function(&MyClass::setData) + ); + +.. warning:: + + Code with invalid return value policies might access uninitialized memory or + free data structures multiple times, which can lead to hard-to-debug + non-determinism and segmentation faults, hence it is worth spending the + time to understand all the different options in the table above. + +.. note:: + + One important aspect of the above policies is that they only apply to + instances which pybind11 has *not* seen before, in which case the policy + clarifies essential questions about the return value's lifetime and + ownership. When pybind11 knows the instance already (as identified by its + type and address in memory), it will return the existing Python object + wrapper rather than creating a new copy. + +.. note:: + + The next section on :ref:`call_policies` discusses *call policies* that can be + specified *in addition* to a return value policy from the list above. Call + policies indicate reference relationships that can involve both return values + and parameters of functions. + +.. note:: + + As an alternative to elaborate call policies and lifetime management logic, + consider using smart pointers (see the section on :ref:`smart_pointers` for + details). Smart pointers can tell whether an object is still referenced from + C++ or Python, which generally eliminates the kinds of inconsistencies that + can lead to crashes or undefined behavior. For functions returning smart + pointers, it is not necessary to specify a return value policy. + +.. _call_policies: + +Additional call policies +======================== + +In addition to the above return value policies, further *call policies* can be +specified to indicate dependencies between parameters or ensure a certain state +for the function call. + +Keep alive +---------- + +In general, this policy is required when the C++ object is any kind of container +and another object is being added to the container. ``keep_alive`` +indicates that the argument with index ``Patient`` should be kept alive at least +until the argument with index ``Nurse`` is freed by the garbage collector. Argument +indices start at one, while zero refers to the return value. For methods, index +``1`` refers to the implicit ``this`` pointer, while regular arguments begin at +index ``2``. Arbitrarily many call policies can be specified. When a ``Nurse`` +with value ``None`` is detected at runtime, the call policy does nothing. + +When the nurse is not a pybind11-registered type, the implementation internally +relies on the ability to create a *weak reference* to the nurse object. When +the nurse object is not a pybind11-registered type and does not support weak +references, an exception will be thrown. + +If you use an incorrect argument index, you will get a ``RuntimeError`` saying +``Could not activate keep_alive!``. You should review the indices you're using. + +Consider the following example: here, the binding code for a list append +operation ties the lifetime of the newly added element to the underlying +container: + +.. code-block:: cpp + + py::class_(m, "List") + .def("append", &List::append, py::keep_alive<1, 2>()); + +For consistency, the argument indexing is identical for constructors. Index +``1`` still refers to the implicit ``this`` pointer, i.e. the object which is +being constructed. Index ``0`` refers to the return type which is presumed to +be ``void`` when a constructor is viewed like a function. The following example +ties the lifetime of the constructor element to the constructed object: + +.. code-block:: cpp + + py::class_(m, "Nurse") + .def(py::init(), py::keep_alive<1, 2>()); + +.. note:: + + ``keep_alive`` is analogous to the ``with_custodian_and_ward`` (if Nurse, + Patient != 0) and ``with_custodian_and_ward_postcall`` (if Nurse/Patient == + 0) policies from Boost.Python. + +Call guard +---------- + +The ``call_guard`` policy allows any scope guard type ``T`` to be placed +around the function call. For example, this definition: + +.. code-block:: cpp + + m.def("foo", foo, py::call_guard()); + +is equivalent to the following pseudocode: + +.. code-block:: cpp + + m.def("foo", [](args...) { + T scope_guard; + return foo(args...); // forwarded arguments + }); + +The only requirement is that ``T`` is default-constructible, but otherwise any +scope guard will work. This is very useful in combination with ``gil_scoped_release``. +See :ref:`gil`. + +Multiple guards can also be specified as ``py::call_guard``. The +constructor order is left to right and destruction happens in reverse. + +.. seealso:: + + The file :file:`tests/test_call_policies.cpp` contains a complete example + that demonstrates using `keep_alive` and `call_guard` in more detail. + +.. _python_objects_as_args: + +Python objects as arguments +=========================== + +pybind11 exposes all major Python types using thin C++ wrapper classes. These +wrapper classes can also be used as parameters of functions in bindings, which +makes it possible to directly work with native Python types on the C++ side. +For instance, the following statement iterates over a Python ``dict``: + +.. code-block:: cpp + + void print_dict(const py::dict& dict) { + /* Easily interact with Python types */ + for (auto item : dict) + std::cout << "key=" << std::string(py::str(item.first)) << ", " + << "value=" << std::string(py::str(item.second)) << std::endl; + } + +It can be exported: + +.. code-block:: cpp + + m.def("print_dict", &print_dict); + +And used in Python as usual: + +.. code-block:: pycon + + >>> print_dict({"foo": 123, "bar": "hello"}) + key=foo, value=123 + key=bar, value=hello + +For more information on using Python objects in C++, see :doc:`/advanced/pycpp/index`. + +Accepting \*args and \*\*kwargs +=============================== + +Python provides a useful mechanism to define functions that accept arbitrary +numbers of arguments and keyword arguments: + +.. code-block:: python + + def generic(*args, **kwargs): + ... # do something with args and kwargs + +Such functions can also be created using pybind11: + +.. code-block:: cpp + + void generic(py::args args, const py::kwargs& kwargs) { + /// .. do something with args + if (kwargs) + /// .. do something with kwargs + } + + /// Binding code + m.def("generic", &generic); + +The class ``py::args`` derives from ``py::tuple`` and ``py::kwargs`` derives +from ``py::dict``. + +You may also use just one or the other, and may combine these with other +arguments. Note, however, that ``py::kwargs`` must always be the last argument +of the function, and ``py::args`` implies that any further arguments are +keyword-only (see :ref:`keyword_only_arguments`). + +Please refer to the other examples for details on how to iterate over these, +and on how to cast their entries into C++ objects. A demonstration is also +available in ``tests/test_kwargs_and_defaults.cpp``. + +.. note:: + + When combining \*args or \*\*kwargs with :ref:`keyword_args` you should + *not* include ``py::arg`` tags for the ``py::args`` and ``py::kwargs`` + arguments. + +Default arguments revisited +=========================== + +The section on :ref:`default_args` previously discussed basic usage of default +arguments using pybind11. One noteworthy aspect of their implementation is that +default arguments are converted to Python objects right at declaration time. +Consider the following example: + +.. code-block:: cpp + + py::class_("MyClass") + .def("myFunction", py::arg("arg") = SomeType(123)); + +In this case, pybind11 must already be set up to deal with values of the type +``SomeType`` (via a prior instantiation of ``py::class_``), or an +exception will be thrown. + +Another aspect worth highlighting is that the "preview" of the default argument +in the function signature is generated using the object's ``__repr__`` method. +If not available, the signature may not be very helpful, e.g.: + +.. code-block:: pycon + + FUNCTIONS + ... + | myFunction(...) + | Signature : (MyClass, arg : SomeType = ) -> NoneType + ... + +The first way of addressing this is by defining ``SomeType.__repr__``. +Alternatively, it is possible to specify the human-readable preview of the +default argument manually using the ``arg_v`` notation: + +.. code-block:: cpp + + py::class_("MyClass") + .def("myFunction", py::arg_v("arg", SomeType(123), "SomeType(123)")); + +Sometimes it may be necessary to pass a null pointer value as a default +argument. In this case, remember to cast it to the underlying type in question, +like so: + +.. code-block:: cpp + + py::class_("MyClass") + .def("myFunction", py::arg("arg") = static_cast(nullptr)); + +.. _keyword_only_arguments: + +Keyword-only arguments +====================== + +Python implements keyword-only arguments by specifying an unnamed ``*`` +argument in a function definition: + +.. code-block:: python + + def f(a, *, b): # a can be positional or via keyword; b must be via keyword + pass + + + f(a=1, b=2) # good + f(b=2, a=1) # good + f(1, b=2) # good + f(1, 2) # TypeError: f() takes 1 positional argument but 2 were given + +Pybind11 provides a ``py::kw_only`` object that allows you to implement +the same behaviour by specifying the object between positional and keyword-only +argument annotations when registering the function: + +.. code-block:: cpp + + m.def("f", [](int a, int b) { /* ... */ }, + py::arg("a"), py::kw_only(), py::arg("b")); + +.. versionadded:: 2.6 + +A ``py::args`` argument implies that any following arguments are keyword-only, +as if ``py::kw_only()`` had been specified in the same relative location of the +argument list as the ``py::args`` argument. The ``py::kw_only()`` may be +included to be explicit about this, but is not required. + +.. versionchanged:: 2.9 + This can now be combined with ``py::args``. Before, ``py::args`` could only + occur at the end of the argument list, or immediately before a ``py::kwargs`` + argument at the end. + + +Positional-only arguments +========================= + +Python 3.8 introduced a new positional-only argument syntax, using ``/`` in the +function definition (note that this has been a convention for CPython +positional arguments, such as in ``pow()``, since Python 2). You can +do the same thing in any version of Python using ``py::pos_only()``: + +.. code-block:: cpp + + m.def("f", [](int a, int b) { /* ... */ }, + py::arg("a"), py::pos_only(), py::arg("b")); + +You now cannot give argument ``a`` by keyword. This can be combined with +keyword-only arguments, as well. + +.. versionadded:: 2.6 + +.. _nonconverting_arguments: + +Non-converting arguments +======================== + +Certain argument types may support conversion from one type to another. Some +examples of conversions are: + +* :ref:`implicit_conversions` declared using ``py::implicitly_convertible()`` +* Calling a method accepting a double with an integer argument +* Calling a ``std::complex`` argument with a non-complex python type + (for example, with a float). (Requires the optional ``pybind11/complex.h`` + header). +* Calling a function taking an Eigen matrix reference with a numpy array of the + wrong type or of an incompatible data layout. (Requires the optional + ``pybind11/eigen.h`` header). + +This behaviour is sometimes undesirable: the binding code may prefer to raise +an error rather than convert the argument. This behaviour can be obtained +through ``py::arg`` by calling the ``.noconvert()`` method of the ``py::arg`` +object, such as: + +.. code-block:: cpp + + m.def("floats_only", [](double f) { return 0.5 * f; }, py::arg("f").noconvert()); + m.def("floats_preferred", [](double f) { return 0.5 * f; }, py::arg("f")); + +Attempting the call the second function (the one without ``.noconvert()``) with +an integer will succeed, but attempting to call the ``.noconvert()`` version +will fail with a ``TypeError``: + +.. code-block:: pycon + + >>> floats_preferred(4) + 2.0 + >>> floats_only(4) + Traceback (most recent call last): + File "", line 1, in + TypeError: floats_only(): incompatible function arguments. The following argument types are supported: + 1. (f: float) -> float + + Invoked with: 4 + +You may, of course, combine this with the :var:`_a` shorthand notation (see +:ref:`keyword_args`) and/or :ref:`default_args`. It is also permitted to omit +the argument name by using the ``py::arg()`` constructor without an argument +name, i.e. by specifying ``py::arg().noconvert()``. + +.. note:: + + When specifying ``py::arg`` options it is necessary to provide the same + number of options as the bound function has arguments. Thus if you want to + enable no-convert behaviour for just one of several arguments, you will + need to specify a ``py::arg()`` annotation for each argument with the + no-convert argument modified to ``py::arg().noconvert()``. + +.. _none_arguments: + +Allow/Prohibiting None arguments +================================ + +When a C++ type registered with :class:`py::class_` is passed as an argument to +a function taking the instance as pointer or shared holder (e.g. ``shared_ptr`` +or a custom, copyable holder as described in :ref:`smart_pointers`), pybind +allows ``None`` to be passed from Python which results in calling the C++ +function with ``nullptr`` (or an empty holder) for the argument. + +To explicitly enable or disable this behaviour, using the +``.none`` method of the :class:`py::arg` object: + +.. code-block:: cpp + + py::class_(m, "Dog").def(py::init<>()); + py::class_(m, "Cat").def(py::init<>()); + m.def("bark", [](Dog *dog) -> std::string { + if (dog) return "woof!"; /* Called with a Dog instance */ + else return "(no dog)"; /* Called with None, dog == nullptr */ + }, py::arg("dog").none(true)); + m.def("meow", [](Cat *cat) -> std::string { + // Can't be called with None argument + return "meow"; + }, py::arg("cat").none(false)); + +With the above, the Python call ``bark(None)`` will return the string ``"(no +dog)"``, while attempting to call ``meow(None)`` will raise a ``TypeError``: + +.. code-block:: pycon + + >>> from animals import Dog, Cat, bark, meow + >>> bark(Dog()) + 'woof!' + >>> meow(Cat()) + 'meow' + >>> bark(None) + '(no dog)' + >>> meow(None) + Traceback (most recent call last): + File "", line 1, in + TypeError: meow(): incompatible function arguments. The following argument types are supported: + 1. (cat: animals.Cat) -> str + + Invoked with: None + +The default behaviour when the tag is unspecified is to allow ``None``. + +.. note:: + + Even when ``.none(true)`` is specified for an argument, ``None`` will be converted to a + ``nullptr`` *only* for custom and :ref:`opaque ` types. Pointers to built-in types + (``double *``, ``int *``, ...) and STL types (``std::vector *``, ...; if ``pybind11/stl.h`` + is included) are copied when converted to C++ (see :doc:`/advanced/cast/overview`) and will + not allow ``None`` as argument. To pass optional argument of these copied types consider + using ``std::optional`` + +.. _overload_resolution: + +Overload resolution order +========================= + +When a function or method with multiple overloads is called from Python, +pybind11 determines which overload to call in two passes. The first pass +attempts to call each overload without allowing argument conversion (as if +every argument had been specified as ``py::arg().noconvert()`` as described +above). + +If no overload succeeds in the no-conversion first pass, a second pass is +attempted in which argument conversion is allowed (except where prohibited via +an explicit ``py::arg().noconvert()`` attribute in the function definition). + +If the second pass also fails a ``TypeError`` is raised. + +Within each pass, overloads are tried in the order they were registered with +pybind11. If the ``py::prepend()`` tag is added to the definition, a function +can be placed at the beginning of the overload sequence instead, allowing user +overloads to proceed built in functions. + +What this means in practice is that pybind11 will prefer any overload that does +not require conversion of arguments to an overload that does, but otherwise +prefers earlier-defined overloads to later-defined ones. + +.. note:: + + pybind11 does *not* further prioritize based on the number/pattern of + overloaded arguments. That is, pybind11 does not prioritize a function + requiring one conversion over one requiring three, but only prioritizes + overloads requiring no conversion at all to overloads that require + conversion of at least one argument. + +.. versionadded:: 2.6 + + The ``py::prepend()`` tag. + +Binding functions with template parameters +========================================== + +You can bind functions that have template parameters. Here's a function: + +.. code-block:: cpp + + template + void set(T t); + +C++ templates cannot be instantiated at runtime, so you cannot bind the +non-instantiated function: + +.. code-block:: cpp + + // BROKEN (this will not compile) + m.def("set", &set); + +You must bind each instantiated function template separately. You may bind +each instantiation with the same name, which will be treated the same as +an overloaded function: + +.. code-block:: cpp + + m.def("set", &set); + m.def("set", &set); + +Sometimes it's more clear to bind them with separate names, which is also +an option: + +.. code-block:: cpp + + m.def("setInt", &set); + m.def("setString", &set); diff --git a/extern/pybind11/docs/advanced/misc.rst b/extern/pybind11/docs/advanced/misc.rst new file mode 100644 index 000000000..ddd7f3937 --- /dev/null +++ b/extern/pybind11/docs/advanced/misc.rst @@ -0,0 +1,429 @@ +Miscellaneous +############# + +.. _macro_notes: + +General notes regarding convenience macros +========================================== + +pybind11 provides a few convenience macros such as +:func:`PYBIND11_DECLARE_HOLDER_TYPE` and ``PYBIND11_OVERRIDE_*``. Since these +are "just" macros that are evaluated in the preprocessor (which has no concept +of types), they *will* get confused by commas in a template argument; for +example, consider: + +.. code-block:: cpp + + PYBIND11_OVERRIDE(MyReturnType, Class, func) + +The limitation of the C preprocessor interprets this as five arguments (with new +arguments beginning after each comma) rather than three. To get around this, +there are two alternatives: you can use a type alias, or you can wrap the type +using the ``PYBIND11_TYPE`` macro: + +.. code-block:: cpp + + // Version 1: using a type alias + using ReturnType = MyReturnType; + using ClassType = Class; + PYBIND11_OVERRIDE(ReturnType, ClassType, func); + + // Version 2: using the PYBIND11_TYPE macro: + PYBIND11_OVERRIDE(PYBIND11_TYPE(MyReturnType), + PYBIND11_TYPE(Class), func) + +The ``PYBIND11_MAKE_OPAQUE`` macro does *not* require the above workarounds. + +.. _gil: + +Global Interpreter Lock (GIL) +============================= + +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, **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,30,31 + + class PyAnimal : public Animal { + public: + /* Inherit the constructors */ + using Animal::Animal; + + /* Trampoline (need one for each virtual function) */ + std::string go(int n_times) { + /* PYBIND11_OVERRIDE_PURE will acquire the GIL before accessing Python state */ + PYBIND11_OVERRIDE_PURE( + std::string, /* Return type */ + Animal, /* Parent class */ + go, /* Name of function */ + n_times /* Argument(s) */ + ); + } + }; + + PYBIND11_MODULE(example, m) { + py::class_ animal(m, "Animal"); + animal + .def(py::init<>()) + .def("go", &Animal::go); + + py::class_(m, "Dog", animal) + .def(py::init<>()); + + m.def("call_go", [](Animal *animal) -> std::string { + // 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); + }); + } + +The ``call_go`` wrapper can also be simplified using the ``call_guard`` policy +(see :ref:`call_policies`) which yields the same result: + +.. code-block:: cpp + + 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. +================================================================== + +Please refer to the supplemental example for details. + +.. seealso:: + + The file :file:`tests/test_sequences_and_iterators.cpp` contains a + complete example that shows how to bind a sequence data type, including + length queries (``__len__``), iterators (``__iter__``), the slicing + protocol and other kinds of useful operations. + + +Partitioning code over multiple extension modules +================================================= + +It's straightforward to split binding code over multiple extension modules, +while referencing types that are declared elsewhere. Everything "just" works +without any special precautions. One exception to this rule occurs when +extending a type declared in another extension module. Recall the basic example +from Section :ref:`inheritance`. + +.. code-block:: cpp + + py::class_ pet(m, "Pet"); + pet.def(py::init()) + .def_readwrite("name", &Pet::name); + + py::class_(m, "Dog", pet /* <- specify parent */) + .def(py::init()) + .def("bark", &Dog::bark); + +Suppose now that ``Pet`` bindings are defined in a module named ``basic``, +whereas the ``Dog`` bindings are defined somewhere else. The challenge is of +course that the variable ``pet`` is not available anymore though it is needed +to indicate the inheritance relationship to the constructor of ``class_``. +However, it can be acquired as follows: + +.. code-block:: cpp + + py::object pet = (py::object) py::module_::import("basic").attr("Pet"); + + py::class_(m, "Dog", pet) + .def(py::init()) + .def("bark", &Dog::bark); + +Alternatively, you can specify the base class as a template parameter option to +``class_``, which performs an automated lookup of the corresponding Python +type. Like the above code, however, this also requires invoking the ``import`` +function once to ensure that the pybind11 binding code of the module ``basic`` +has been executed: + +.. code-block:: cpp + + py::module_::import("basic"); + + py::class_(m, "Dog") + .def(py::init()) + .def("bark", &Dog::bark); + +Naturally, both methods will fail when there are cyclic dependencies. + +Note that pybind11 code compiled with hidden-by-default symbol visibility (e.g. +via the command line flag ``-fvisibility=hidden`` on GCC/Clang), which is +required for proper pybind11 functionality, can interfere with the ability to +access types defined in another extension module. Working around this requires +manually exporting types that are accessed by multiple extension modules; +pybind11 provides a macro to do just this: + +.. code-block:: cpp + + class PYBIND11_EXPORT Dog : public Animal { + ... + }; + +Note also that it is possible (although would rarely be required) to share arbitrary +C++ objects between extension modules at runtime. Internal library data is shared +between modules using capsule machinery [#f6]_ which can be also utilized for +storing, modifying and accessing user-defined data. Note that an extension module +will "see" other extensions' data if and only if they were built with the same +pybind11 version. Consider the following example: + +.. code-block:: cpp + + auto data = reinterpret_cast(py::get_shared_data("mydata")); + if (!data) + data = static_cast(py::set_shared_data("mydata", new MyData(42))); + +If the above snippet was used in several separately compiled extension modules, +the first one to be imported would create a ``MyData`` instance and associate +a ``"mydata"`` key with a pointer to it. Extensions that are imported later +would be then able to access the data behind the same pointer. + +.. [#f6] https://docs.python.org/3/extending/extending.html#using-capsules + +Module Destructors +================== + +pybind11 does not provide an explicit mechanism to invoke cleanup code at +module destruction time. In rare cases where such functionality is required, it +is possible to emulate it using Python capsules or weak references with a +destruction callback. + +.. code-block:: cpp + + auto cleanup_callback = []() { + // perform cleanup here -- this function is called with the GIL held + }; + + m.add_object("_cleanup", py::capsule(cleanup_callback)); + +This approach has the potential downside that instances of classes exposed +within the module may still be alive when the cleanup callback is invoked +(whether this is acceptable will generally depend on the application). + +Alternatively, the capsule may also be stashed within a type object, which +ensures that it not called before all instances of that type have been +collected: + +.. code-block:: cpp + + auto cleanup_callback = []() { /* ... */ }; + m.attr("BaseClass").attr("_cleanup") = py::capsule(cleanup_callback); + +Both approaches also expose a potentially dangerous ``_cleanup`` attribute in +Python, which may be undesirable from an API standpoint (a premature explicit +call from Python might lead to undefined behavior). Yet another approach that +avoids this issue involves weak reference with a cleanup callback: + +.. code-block:: cpp + + // Register a callback function that is invoked when the BaseClass object is collected + py::cpp_function cleanup_callback( + [](py::handle weakref) { + // perform cleanup here -- this function is called with the GIL held + + weakref.dec_ref(); // release weak reference + } + ); + + // Create a weak reference with a cleanup callback and initially leak it + (void) py::weakref(m.attr("BaseClass"), cleanup_callback).release(); + +.. note:: + + PyPy does not garbage collect objects when the interpreter exits. An alternative + approach (which also works on CPython) is to use the :py:mod:`atexit` module [#f7]_, + for example: + + .. code-block:: cpp + + auto atexit = py::module_::import("atexit"); + atexit.attr("register")(py::cpp_function([]() { + // perform cleanup here -- this function is called with the GIL held + })); + + .. [#f7] https://docs.python.org/3/library/atexit.html + + +Generating documentation using Sphinx +===================================== + +Sphinx [#f4]_ has the ability to inspect the signatures and documentation +strings in pybind11-based extension modules to automatically generate beautiful +documentation in a variety formats. The python_example repository [#f5]_ contains a +simple example repository which uses this approach. + +There are two potential gotchas when using this approach: first, make sure that +the resulting strings do not contain any :kbd:`TAB` characters, which break the +docstring parsing routines. You may want to use C++11 raw string literals, +which are convenient for multi-line comments. Conveniently, any excess +indentation will be automatically be removed by Sphinx. However, for this to +work, it is important that all lines are indented consistently, i.e.: + +.. code-block:: cpp + + // ok + m.def("foo", &foo, R"mydelimiter( + The foo function + + Parameters + ---------- + )mydelimiter"); + + // *not ok* + m.def("foo", &foo, R"mydelimiter(The foo function + + Parameters + ---------- + )mydelimiter"); + +By default, pybind11 automatically generates and prepends a signature to the docstring of a function +registered with ``module_::def()`` and ``class_::def()``. Sometimes this +behavior is not desirable, because you want to provide your own signature or remove +the docstring completely to exclude the function from the Sphinx documentation. +The class ``options`` allows you to selectively suppress auto-generated signatures: + +.. code-block:: cpp + + PYBIND11_MODULE(example, m) { + py::options options; + options.disable_function_signatures(); + + 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. + +.. [#f4] http://www.sphinx-doc.org +.. [#f5] http://github.com/pybind/python_example + +.. _avoiding-cpp-types-in-docstrings: + +Avoiding C++ types in docstrings +================================ + +Docstrings are generated at the time of the declaration, e.g. when ``.def(...)`` is called. +At this point parameter and return types should be known to pybind11. +If a custom type is not exposed yet through a ``py::class_`` constructor or a custom type caster, +its C++ type name will be used instead to generate the signature in the docstring: + +.. code-block:: text + + | __init__(...) + | __init__(self: example.Foo, arg0: ns::Bar) -> None + ^^^^^^^ + + +This limitation can be circumvented by ensuring that C++ classes are registered with pybind11 +before they are used as a parameter or return type of a function: + +.. code-block:: cpp + + PYBIND11_MODULE(example, m) { + + auto pyFoo = py::class_(m, "Foo"); + auto pyBar = py::class_(m, "Bar"); + + pyFoo.def(py::init()); + pyBar.def(py::init()); + } + +Setting inner type hints in docstrings +====================================== + +When you use pybind11 wrappers for ``list``, ``dict``, and other generic python +types, the docstring will just display the generic type. You can convey the +inner types in the docstring by using a special 'typed' version of the generic +type. + +.. code-block:: cpp + + PYBIND11_MODULE(example, m) { + m.def("pass_list_of_str", [](py::typing::List arg) { + // arg can be used just like py::list + )); + } + +The resulting docstring will be ``pass_list_of_str(arg0: list[str]) -> None``. + +The following special types are available in ``pybind11/typing.h``: + +* ``py::Tuple`` +* ``py::Dict`` +* ``py::List`` +* ``py::Set`` +* ``py::Callable`` + +.. warning:: Just like in python, these are merely hints. They don't actually + enforce the types of their contents at runtime or compile time. diff --git a/extern/pybind11/docs/advanced/pycpp/index.rst b/extern/pybind11/docs/advanced/pycpp/index.rst new file mode 100644 index 000000000..6885bdcff --- /dev/null +++ b/extern/pybind11/docs/advanced/pycpp/index.rst @@ -0,0 +1,13 @@ +Python C++ interface +#################### + +pybind11 exposes Python types and functions using thin C++ wrappers, which +makes it possible to conveniently call Python code from C++ without resorting +to Python's C API. + +.. toctree:: + :maxdepth: 2 + + object + numpy + utilities diff --git a/extern/pybind11/docs/advanced/pycpp/numpy.rst b/extern/pybind11/docs/advanced/pycpp/numpy.rst new file mode 100644 index 000000000..07c969305 --- /dev/null +++ b/extern/pybind11/docs/advanced/pycpp/numpy.rst @@ -0,0 +1,455 @@ +.. _numpy: + +NumPy +##### + +Buffer protocol +=============== + +Python supports an extremely general and convenient approach for exchanging +data between plugin libraries. Types can expose a buffer view [#f2]_, which +provides fast direct access to the raw internal data representation. Suppose we +want to bind the following simplistic Matrix class: + +.. code-block:: cpp + + class Matrix { + public: + Matrix(size_t rows, size_t cols) : m_rows(rows), m_cols(cols) { + m_data = new float[rows*cols]; + } + float *data() { return m_data; } + size_t rows() const { return m_rows; } + size_t cols() const { return m_cols; } + private: + size_t m_rows, m_cols; + float *m_data; + }; + +The following binding code exposes the ``Matrix`` contents as a buffer object, +making it possible to cast Matrices into NumPy arrays. It is even possible to +completely avoid copy operations with Python expressions like +``np.array(matrix_instance, copy = False)``. + +.. code-block:: cpp + + py::class_(m, "Matrix", py::buffer_protocol()) + .def_buffer([](Matrix &m) -> py::buffer_info { + return py::buffer_info( + m.data(), /* Pointer to buffer */ + sizeof(float), /* Size of one scalar */ + py::format_descriptor::format(), /* Python struct-style format descriptor */ + 2, /* Number of dimensions */ + { m.rows(), m.cols() }, /* Buffer dimensions */ + { sizeof(float) * m.cols(), /* Strides (in bytes) for each index */ + sizeof(float) } + ); + }); + +Supporting the buffer protocol in a new type involves specifying the special +``py::buffer_protocol()`` tag in the ``py::class_`` constructor and calling the +``def_buffer()`` method with a lambda function that creates a +``py::buffer_info`` description record on demand describing a given matrix +instance. The contents of ``py::buffer_info`` mirror the Python buffer protocol +specification. + +.. code-block:: cpp + + struct buffer_info { + void *ptr; + py::ssize_t itemsize; + std::string format; + py::ssize_t ndim; + std::vector shape; + std::vector strides; + }; + +To create a C++ function that can take a Python buffer object as an argument, +simply use the type ``py::buffer`` as one of its arguments. Buffers can exist +in a great variety of configurations, hence some safety checks are usually +necessary in the function body. Below, you can see a basic example on how to +define a custom constructor for the Eigen double precision matrix +(``Eigen::MatrixXd``) type, which supports initialization from compatible +buffer objects (e.g. a NumPy matrix). + +.. code-block:: cpp + + /* Bind MatrixXd (or some other Eigen type) to Python */ + typedef Eigen::MatrixXd Matrix; + + typedef Matrix::Scalar Scalar; + constexpr bool rowMajor = Matrix::Flags & Eigen::RowMajorBit; + + py::class_(m, "Matrix", py::buffer_protocol()) + .def(py::init([](py::buffer b) { + typedef Eigen::Stride Strides; + + /* Request a buffer descriptor from Python */ + py::buffer_info info = b.request(); + + /* Some basic validation checks ... */ + if (info.format != py::format_descriptor::format()) + throw std::runtime_error("Incompatible format: expected a double array!"); + + if (info.ndim != 2) + throw std::runtime_error("Incompatible buffer dimension!"); + + auto strides = Strides( + info.strides[rowMajor ? 0 : 1] / (py::ssize_t)sizeof(Scalar), + info.strides[rowMajor ? 1 : 0] / (py::ssize_t)sizeof(Scalar)); + + auto map = Eigen::Map( + static_cast(info.ptr), info.shape[0], info.shape[1], strides); + + return Matrix(map); + })); + +For reference, the ``def_buffer()`` call for this Eigen data type should look +as follows: + +.. code-block:: cpp + + .def_buffer([](Matrix &m) -> py::buffer_info { + return py::buffer_info( + m.data(), /* Pointer to buffer */ + sizeof(Scalar), /* Size of one scalar */ + py::format_descriptor::format(), /* Python struct-style format descriptor */ + 2, /* Number of dimensions */ + { m.rows(), m.cols() }, /* Buffer dimensions */ + { sizeof(Scalar) * (rowMajor ? m.cols() : 1), + sizeof(Scalar) * (rowMajor ? 1 : m.rows()) } + /* Strides (in bytes) for each index */ + ); + }) + +For a much easier approach of binding Eigen types (although with some +limitations), refer to the section on :doc:`/advanced/cast/eigen`. + +.. seealso:: + + The file :file:`tests/test_buffers.cpp` contains a complete example + that demonstrates using the buffer protocol with pybind11 in more detail. + +.. [#f2] http://docs.python.org/3/c-api/buffer.html + +Arrays +====== + +By exchanging ``py::buffer`` with ``py::array`` in the above snippet, we can +restrict the function so that it only accepts NumPy arrays (rather than any +type of Python object satisfying the buffer protocol). + +In many situations, we want to define a function which only accepts a NumPy +array of a certain data type. This is possible via the ``py::array_t`` +template. For instance, the following function requires the argument to be a +NumPy array containing double precision values. + +.. code-block:: cpp + + void f(py::array_t array); + +When it is invoked with a different type (e.g. an integer or a list of +integers), the binding code will attempt to cast the input into a NumPy array +of the requested type. This feature requires the :file:`pybind11/numpy.h` +header to be included. Note that :file:`pybind11/numpy.h` does not depend on +the NumPy headers, and thus can be used without declaring a build-time +dependency on NumPy; NumPy>=1.7.0 is a runtime dependency. + +Data in NumPy arrays is not guaranteed to packed in a dense manner; +furthermore, entries can be separated by arbitrary column and row strides. +Sometimes, it can be useful to require a function to only accept dense arrays +using either the C (row-major) or Fortran (column-major) ordering. This can be +accomplished via a second template argument with values ``py::array::c_style`` +or ``py::array::f_style``. + +.. code-block:: cpp + + void f(py::array_t array); + +The ``py::array::forcecast`` argument is the default value of the second +template parameter, and it ensures that non-conforming arguments are converted +into an array satisfying the specified requirements instead of trying the next +function overload. + +There are several methods on arrays; the methods listed below under references +work, as well as the following functions based on the NumPy API: + +- ``.dtype()`` returns the type of the contained values. + +- ``.strides()`` returns a pointer to the strides of the array (optionally pass + an integer axis to get a number). + +- ``.flags()`` returns the flag settings. ``.writable()`` and ``.owndata()`` + are directly available. + +- ``.offset_at()`` returns the offset (optionally pass indices). + +- ``.squeeze()`` returns a view with length-1 axes removed. + +- ``.view(dtype)`` returns a view of the array with a different dtype. + +- ``.reshape({i, j, ...})`` returns a view of the array with a different shape. + ``.resize({...})`` is also available. + +- ``.index_at(i, j, ...)`` gets the count from the beginning to a given index. + + +There are also several methods for getting references (described below). + +Structured types +================ + +In order for ``py::array_t`` to work with structured (record) types, we first +need to register the memory layout of the type. This can be done via +``PYBIND11_NUMPY_DTYPE`` macro, called in the plugin definition code, which +expects the type followed by field names: + +.. code-block:: cpp + + struct A { + int x; + double y; + }; + + struct B { + int z; + A a; + }; + + // ... + PYBIND11_MODULE(test, m) { + // ... + + PYBIND11_NUMPY_DTYPE(A, x, y); + PYBIND11_NUMPY_DTYPE(B, z, a); + /* now both A and B can be used as template arguments to py::array_t */ + } + +The structure should consist of fundamental arithmetic types, ``std::complex``, +previously registered substructures, and arrays of any of the above. Both C++ +arrays and ``std::array`` are supported. While there is a static assertion to +prevent many types of unsupported structures, it is still the user's +responsibility to use only "plain" structures that can be safely manipulated as +raw memory without violating invariants. + +Vectorizing functions +===================== + +Suppose we want to bind a function with the following signature to Python so +that it can process arbitrary NumPy array arguments (vectors, matrices, general +N-D arrays) in addition to its normal arguments: + +.. code-block:: cpp + + double my_func(int x, float y, double z); + +After including the ``pybind11/numpy.h`` header, this is extremely simple: + +.. code-block:: cpp + + m.def("vectorized_func", py::vectorize(my_func)); + +Invoking the function like below causes 4 calls to be made to ``my_func`` with +each of the array elements. The significant advantage of this compared to +solutions like ``numpy.vectorize()`` is that the loop over the elements runs +entirely on the C++ side and can be crunched down into a tight, optimized loop +by the compiler. The result is returned as a NumPy array of type +``numpy.dtype.float64``. + +.. code-block:: pycon + + >>> x = np.array([[1, 3], [5, 7]]) + >>> y = np.array([[2, 4], [6, 8]]) + >>> z = 3 + >>> result = vectorized_func(x, y, z) + +The scalar argument ``z`` is transparently replicated 4 times. The input +arrays ``x`` and ``y`` are automatically converted into the right types (they +are of type ``numpy.dtype.int64`` but need to be ``numpy.dtype.int32`` and +``numpy.dtype.float32``, respectively). + +.. note:: + + Only arithmetic, complex, and POD types passed by value or by ``const &`` + reference are vectorized; all other arguments are passed through as-is. + Functions taking rvalue reference arguments cannot be vectorized. + +In cases where the computation is too complicated to be reduced to +``vectorize``, it will be necessary to create and access the buffer contents +manually. The following snippet contains a complete example that shows how this +works (the code is somewhat contrived, since it could have been done more +simply using ``vectorize``). + +.. code-block:: cpp + + #include + #include + + namespace py = pybind11; + + py::array_t add_arrays(py::array_t input1, py::array_t input2) { + py::buffer_info buf1 = input1.request(), buf2 = input2.request(); + + if (buf1.ndim != 1 || buf2.ndim != 1) + throw std::runtime_error("Number of dimensions must be one"); + + if (buf1.size != buf2.size) + throw std::runtime_error("Input shapes must match"); + + /* No pointer is passed, so NumPy will allocate the buffer */ + auto result = py::array_t(buf1.size); + + py::buffer_info buf3 = result.request(); + + double *ptr1 = static_cast(buf1.ptr); + double *ptr2 = static_cast(buf2.ptr); + double *ptr3 = static_cast(buf3.ptr); + + for (size_t idx = 0; idx < buf1.shape[0]; idx++) + ptr3[idx] = ptr1[idx] + ptr2[idx]; + + return result; + } + + PYBIND11_MODULE(test, m) { + m.def("add_arrays", &add_arrays, "Add two NumPy arrays"); + } + +.. seealso:: + + The file :file:`tests/test_numpy_vectorize.cpp` contains a complete + example that demonstrates using :func:`vectorize` in more detail. + +Direct access +============= + +For performance reasons, particularly when dealing with very large arrays, it +is often desirable to directly access array elements without internal checking +of dimensions and bounds on every access when indices are known to be already +valid. To avoid such checks, the ``array`` class and ``array_t`` template +class offer an unchecked proxy object that can be used for this unchecked +access through the ``unchecked`` and ``mutable_unchecked`` methods, +where ``N`` gives the required dimensionality of the array: + +.. code-block:: cpp + + m.def("sum_3d", [](py::array_t x) { + auto r = x.unchecked<3>(); // x must have ndim = 3; can be non-writeable + double sum = 0; + for (py::ssize_t i = 0; i < r.shape(0); i++) + for (py::ssize_t j = 0; j < r.shape(1); j++) + for (py::ssize_t k = 0; k < r.shape(2); k++) + sum += r(i, j, k); + return sum; + }); + m.def("increment_3d", [](py::array_t x) { + auto r = x.mutable_unchecked<3>(); // Will throw if ndim != 3 or flags.writeable is false + for (py::ssize_t i = 0; i < r.shape(0); i++) + for (py::ssize_t j = 0; j < r.shape(1); j++) + for (py::ssize_t k = 0; k < r.shape(2); k++) + r(i, j, k) += 1.0; + }, py::arg().noconvert()); + +To obtain the proxy from an ``array`` object, you must specify both the data +type and number of dimensions as template arguments, such as ``auto r = +myarray.mutable_unchecked()``. + +If the number of dimensions is not known at compile time, you can omit the +dimensions template parameter (i.e. calling ``arr_t.unchecked()`` or +``arr.unchecked()``. This will give you a proxy object that works in the +same way, but results in less optimizable code and thus a small efficiency +loss in tight loops. + +Note that the returned proxy object directly references the array's data, and +only reads its shape, strides, and writeable flag when constructed. You must +take care to ensure that the referenced array is not destroyed or reshaped for +the duration of the returned object, typically by limiting the scope of the +returned instance. + +The returned proxy object supports some of the same methods as ``py::array`` so +that it can be used as a drop-in replacement for some existing, index-checked +uses of ``py::array``: + +- ``.ndim()`` returns the number of dimensions + +- ``.data(1, 2, ...)`` and ``r.mutable_data(1, 2, ...)``` returns a pointer to + the ``const T`` or ``T`` data, respectively, at the given indices. The + latter is only available to proxies obtained via ``a.mutable_unchecked()``. + +- ``.itemsize()`` returns the size of an item in bytes, i.e. ``sizeof(T)``. + +- ``.ndim()`` returns the number of dimensions. + +- ``.shape(n)`` returns the size of dimension ``n`` + +- ``.size()`` returns the total number of elements (i.e. the product of the shapes). + +- ``.nbytes()`` returns the number of bytes used by the referenced elements + (i.e. ``itemsize()`` times ``size()``). + +.. seealso:: + + The file :file:`tests/test_numpy_array.cpp` contains additional examples + demonstrating the use of this feature. + +Ellipsis +======== + +Python provides a convenient ``...`` ellipsis notation that is often used to +slice multidimensional arrays. For instance, the following snippet extracts the +middle dimensions of a tensor with the first and last index set to zero. + +.. code-block:: python + + a = ... # a NumPy array + b = a[0, ..., 0] + +The function ``py::ellipsis()`` function can be used to perform the same +operation on the C++ side: + +.. code-block:: cpp + + py::array a = /* A NumPy array */; + py::array b = a[py::make_tuple(0, py::ellipsis(), 0)]; + + +Memory view +=========== + +For a case when we simply want to provide a direct accessor to C/C++ buffer +without a concrete class object, we can return a ``memoryview`` object. Suppose +we wish to expose a ``memoryview`` for 2x4 uint8_t array, we can do the +following: + +.. code-block:: cpp + + const uint8_t buffer[] = { + 0, 1, 2, 3, + 4, 5, 6, 7 + }; + m.def("get_memoryview2d", []() { + return py::memoryview::from_buffer( + buffer, // buffer pointer + { 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 +buffer. Using a ``memoryview`` created in this way after deleting the buffer in +C++ side results in undefined behavior. + +We can also use ``memoryview::from_memory`` for a simple 1D contiguous buffer: + +.. code-block:: cpp + + m.def("get_memoryview1d", []() { + return py::memoryview::from_memory( + buffer, // buffer pointer + sizeof(uint8_t) * 8 // buffer size + ); + }); + +.. versionchanged:: 2.6 + ``memoryview::from_memory`` added. diff --git a/extern/pybind11/docs/advanced/pycpp/object.rst b/extern/pybind11/docs/advanced/pycpp/object.rst new file mode 100644 index 000000000..93e1a94d8 --- /dev/null +++ b/extern/pybind11/docs/advanced/pycpp/object.rst @@ -0,0 +1,286 @@ +Python types +############ + +.. _wrappers: + +Available wrappers +================== + +All major Python types are available as thin C++ wrapper classes. These +can also be used as function parameters -- see :ref:`python_objects_as_args`. + +Available types include :class:`handle`, :class:`object`, :class:`bool_`, +:class:`int_`, :class:`float_`, :class:`str`, :class:`bytes`, :class:`tuple`, +:class:`list`, :class:`dict`, :class:`slice`, :class:`none`, :class:`capsule`, +:class:`iterable`, :class:`iterator`, :class:`function`, :class:`buffer`, +:class:`array`, and :class:`array_t`. + +.. warning:: + + Be sure to review the :ref:`pytypes_gotchas` before using this heavily in + your C++ API. + +.. _instantiating_compound_types: + +Instantiating compound Python types from C++ +============================================ + +Dictionaries can be initialized in the :class:`dict` constructor: + +.. code-block:: cpp + + using namespace pybind11::literals; // to bring in the `_a` literal + py::dict d("spam"_a=py::none(), "eggs"_a=42); + +A tuple of python objects can be instantiated using :func:`py::make_tuple`: + +.. code-block:: cpp + + py::tuple tup = py::make_tuple(42, py::none(), "spam"); + +Each element is converted to a supported Python type. + +A `simple namespace`_ can be instantiated using + +.. code-block:: cpp + + using namespace pybind11::literals; // to bring in the `_a` literal + py::object SimpleNamespace = py::module_::import("types").attr("SimpleNamespace"); + py::object ns = SimpleNamespace("spam"_a=py::none(), "eggs"_a=42); + +Attributes on a namespace can be modified with the :func:`py::delattr`, +:func:`py::getattr`, and :func:`py::setattr` functions. Simple namespaces can +be useful as lightweight stand-ins for class instances. + +.. _simple namespace: https://docs.python.org/3/library/types.html#types.SimpleNamespace + +.. _casting_back_and_forth: + +Casting back and forth +====================== + +In this kind of mixed code, it is often necessary to convert arbitrary C++ +types to Python, which can be done using :func:`py::cast`: + +.. code-block:: cpp + + MyClass *cls = ...; + py::object obj = py::cast(cls); + +The reverse direction uses the following syntax: + +.. code-block:: cpp + + py::object obj = ...; + MyClass *cls = obj.cast(); + +When conversion fails, both directions throw the exception :class:`cast_error`. + +.. _python_libs: + +Accessing Python libraries from C++ +=================================== + +It is also possible to import objects defined in the Python standard +library or available in the current Python environment (``sys.path``) and work +with these in C++. + +This example obtains a reference to the Python ``Decimal`` class. + +.. code-block:: cpp + + // Equivalent to "from decimal import Decimal" + py::object Decimal = py::module_::import("decimal").attr("Decimal"); + +.. code-block:: cpp + + // Try to import scipy + py::object scipy = py::module_::import("scipy"); + return scipy.attr("__version__"); + + +.. _calling_python_functions: + +Calling Python functions +======================== + +It is also possible to call Python classes, functions and methods +via ``operator()``. + +.. code-block:: cpp + + // Construct a Python object of class Decimal + py::object pi = Decimal("3.14159"); + +.. code-block:: cpp + + // Use Python to make our directories + py::object os = py::module_::import("os"); + py::object makedirs = os.attr("makedirs"); + makedirs("/tmp/path/to/somewhere"); + +One can convert the result obtained from Python to a pure C++ version +if a ``py::class_`` or type conversion is defined. + +.. code-block:: cpp + + py::function f = <...>; + py::object result_py = f(1234, "hello", some_instance); + MyClass &result = result_py.cast(); + +.. _calling_python_methods: + +Calling Python methods +======================== + +To call an object's method, one can again use ``.attr`` to obtain access to the +Python method. + +.. code-block:: cpp + + // Calculate e^π in decimal + py::object exp_pi = pi.attr("exp")(); + py::print(py::str(exp_pi)); + +In the example above ``pi.attr("exp")`` is a *bound method*: it will always call +the method for that same instance of the class. Alternately one can create an +*unbound method* via the Python class (instead of instance) and pass the ``self`` +object explicitly, followed by other arguments. + +.. code-block:: cpp + + py::object decimal_exp = Decimal.attr("exp"); + + // Compute the e^n for n=0..4 + for (int n = 0; n < 5; n++) { + py::print(decimal_exp(Decimal(n)); + } + +Keyword arguments +================= + +Keyword arguments are also supported. In Python, there is the usual call syntax: + +.. code-block:: python + + def f(number, say, to): + ... # function code + + + f(1234, say="hello", to=some_instance) # keyword call in Python + +In C++, the same call can be made using: + +.. code-block:: cpp + + using namespace pybind11::literals; // to bring in the `_a` literal + f(1234, "say"_a="hello", "to"_a=some_instance); // keyword call in C++ + +Unpacking arguments +=================== + +Unpacking of ``*args`` and ``**kwargs`` is also possible and can be mixed with +other arguments: + +.. code-block:: cpp + + // * unpacking + py::tuple args = py::make_tuple(1234, "hello", some_instance); + f(*args); + + // ** unpacking + py::dict kwargs = py::dict("number"_a=1234, "say"_a="hello", "to"_a=some_instance); + f(**kwargs); + + // mixed keywords, * and ** unpacking + py::tuple args = py::make_tuple(1234); + py::dict kwargs = py::dict("to"_a=some_instance); + f(*args, "say"_a="hello", **kwargs); + +Generalized unpacking according to PEP448_ is also supported: + +.. code-block:: cpp + + py::dict kwargs1 = py::dict("number"_a=1234); + py::dict kwargs2 = py::dict("to"_a=some_instance); + f(**kwargs1, "say"_a="hello", **kwargs2); + +.. seealso:: + + The file :file:`tests/test_pytypes.cpp` contains a complete + example that demonstrates passing native Python types in more detail. The + file :file:`tests/test_callbacks.cpp` presents a few examples of calling + Python functions from C++, including keywords arguments and unpacking. + +.. _PEP448: https://www.python.org/dev/peps/pep-0448/ + +.. _implicit_casting: + +Implicit casting +================ + +When using the C++ interface for Python types, or calling Python functions, +objects of type :class:`object` are returned. It is possible to invoke implicit +conversions to subclasses like :class:`dict`. The same holds for the proxy objects +returned by ``operator[]`` or ``obj.attr()``. +Casting to subtypes improves code readability and allows values to be passed to +C++ functions that require a specific subtype rather than a generic :class:`object`. + +.. code-block:: cpp + + #include + using namespace pybind11::literals; + + py::module_ os = py::module_::import("os"); + py::module_ path = py::module_::import("os.path"); // like 'import os.path as path' + py::module_ np = py::module_::import("numpy"); // like 'import numpy as np' + + py::str curdir_abs = path.attr("abspath")(path.attr("curdir")); + py::print(py::str("Current directory: ") + curdir_abs); + py::dict environ = os.attr("environ"); + py::print(environ["HOME"]); + py::array_t arr = np.attr("ones")(3, "dtype"_a="float32"); + py::print(py::repr(arr + py::int_(1))); + +These implicit conversions are available for subclasses of :class:`object`; there +is no need to call ``obj.cast()`` explicitly as for custom classes, see +:ref:`casting_back_and_forth`. + +.. note:: + If a trivial conversion via move constructor is not possible, both implicit and + explicit casting (calling ``obj.cast()``) will attempt a "rich" conversion. + For instance, ``py::list env = os.attr("environ");`` will succeed and is + equivalent to the Python code ``env = list(os.environ)`` that produces a + list of the dict keys. + +.. TODO: Adapt text once PR #2349 has landed + +Handling exceptions +=================== + +Python exceptions from wrapper classes will be thrown as a ``py::error_already_set``. +See :ref:`Handling exceptions from Python in C++ +` for more information on handling exceptions +raised when calling C++ wrapper classes. + +.. _pytypes_gotchas: + +Gotchas +======= + +Default-Constructed Wrappers +---------------------------- + +When a wrapper type is default-constructed, it is **not** a valid Python object (i.e. it is not ``py::none()``). It is simply the same as +``PyObject*`` null pointer. To check for this, use +``static_cast(my_wrapper)``. + +Assigning py::none() to wrappers +-------------------------------- + +You may be tempted to use types like ``py::str`` and ``py::dict`` in C++ +signatures (either pure C++, or in bound signatures), and assign them default +values of ``py::none()``. However, in a best case scenario, it will fail fast +because ``None`` is not convertible to that type (e.g. ``py::dict``), or in a +worse case scenario, it will silently work but corrupt the types you want to +work with (e.g. ``py::str(py::none())`` will yield ``"None"`` in Python). diff --git a/extern/pybind11/docs/advanced/pycpp/utilities.rst b/extern/pybind11/docs/advanced/pycpp/utilities.rst new file mode 100644 index 000000000..af0f9cb2b --- /dev/null +++ b/extern/pybind11/docs/advanced/pycpp/utilities.rst @@ -0,0 +1,155 @@ +Utilities +######### + +Using Python's print function in C++ +==================================== + +The usual way to write output in C++ is using ``std::cout`` while in Python one +would use ``print``. Since these methods use different buffers, mixing them can +lead to output order issues. To resolve this, pybind11 modules can use the +:func:`py::print` function which writes to Python's ``sys.stdout`` for consistency. + +Python's ``print`` function is replicated in the C++ API including optional +keyword arguments ``sep``, ``end``, ``file``, ``flush``. Everything works as +expected in Python: + +.. code-block:: cpp + + py::print(1, 2.0, "three"); // 1 2.0 three + py::print(1, 2.0, "three", "sep"_a="-"); // 1-2.0-three + + auto args = py::make_tuple("unpacked", true); + py::print("->", *args, "end"_a="<-"); // -> unpacked True <- + +.. _ostream_redirect: + +Capturing standard output from ostream +====================================== + +Often, a library will use the streams ``std::cout`` and ``std::cerr`` to print, +but this does not play well with Python's standard ``sys.stdout`` and ``sys.stderr`` +redirection. Replacing a library's printing with ``py::print `` may not +be feasible. This can be fixed using a guard around the library function that +redirects output to the corresponding Python streams: + +.. code-block:: cpp + + #include + + ... + + // Add a scoped redirect for your noisy code + m.def("noisy_func", []() { + py::scoped_ostream_redirect stream( + std::cout, // std::ostream& + py::module_::import("sys").attr("stdout") // Python output + ); + call_noisy_func(); + }); + +.. warning:: + + The implementation in ``pybind11/iostream.h`` is NOT thread safe. Multiple + threads writing to a redirected ostream concurrently cause data races + and potentially buffer overflows. Therefore it is currently a requirement + that all (possibly) concurrent redirected ostream writes are protected by + a mutex. #HelpAppreciated: Work on iostream.h thread safety. For more + background see the discussions under + `PR #2982 `_ and + `PR #2995 `_. + +This method respects flushes on the output streams and will flush if needed +when the scoped guard is destroyed. This allows the output to be redirected in +real time, such as to a Jupyter notebook. The two arguments, the C++ stream and +the Python output, are optional, and default to standard output if not given. An +extra type, ``py::scoped_estream_redirect ``, is identical +except for defaulting to ``std::cerr`` and ``sys.stderr``; this can be useful with +``py::call_guard``, which allows multiple items, but uses the default constructor: + +.. code-block:: cpp + + // Alternative: Call single function using call guard + m.def("noisy_func", &call_noisy_function, + py::call_guard()); + +The redirection can also be done in Python with the addition of a context +manager, using the ``py::add_ostream_redirect() `` function: + +.. code-block:: cpp + + py::add_ostream_redirect(m, "ostream_redirect"); + +The name in Python defaults to ``ostream_redirect`` if no name is passed. This +creates the following context manager in Python: + +.. code-block:: python + + with ostream_redirect(stdout=True, stderr=True): + noisy_function() + +It defaults to redirecting both streams, though you can use the keyword +arguments to disable one of the streams if needed. + +.. note:: + + The above methods will not redirect C-level output to file descriptors, such + as ``fprintf``. For those cases, you'll need to redirect the file + descriptors either directly in C or with Python's ``os.dup2`` function + in an operating-system dependent way. + +.. _eval: + +Evaluating Python expressions from strings and files +==================================================== + +pybind11 provides the ``eval``, ``exec`` and ``eval_file`` functions to evaluate +Python expressions and statements. The following example illustrates how they +can be used. + +.. code-block:: cpp + + // At beginning of file + #include + + ... + + // Evaluate in scope of main module + py::object scope = py::module_::import("__main__").attr("__dict__"); + + // Evaluate an isolated expression + int result = py::eval("my_variable + 10", scope).cast(); + + // Evaluate a sequence of statements + py::exec( + "print('Hello')\n" + "print('world!');", + scope); + + // Evaluate the statements in an separate Python file on disk + py::eval_file("script.py", scope); + +C++11 raw string literals are also supported and quite handy for this purpose. +The only requirement is that the first statement must be on a new line following +the raw string delimiter ``R"(``, ensuring all lines have common leading indent: + +.. code-block:: cpp + + py::exec(R"( + x = get_answer() + if x == 42: + print('Hello World!') + else: + print('Bye!') + )", scope + ); + +.. note:: + + `eval` and `eval_file` accept a template parameter that describes how the + string/file should be interpreted. Possible choices include ``eval_expr`` + (isolated expression), ``eval_single_statement`` (a single statement, return + value is always ``none``), and ``eval_statements`` (sequence of statements, + return value is always ``none``). `eval` defaults to ``eval_expr``, + `eval_file` defaults to ``eval_statements`` and `exec` is just a shortcut + for ``eval``. diff --git a/extern/pybind11/docs/advanced/smart_ptrs.rst b/extern/pybind11/docs/advanced/smart_ptrs.rst new file mode 100644 index 000000000..3c40ce123 --- /dev/null +++ b/extern/pybind11/docs/advanced/smart_ptrs.rst @@ -0,0 +1,174 @@ +Smart pointers +############## + +std::unique_ptr +=============== + +Given a class ``Example`` with Python bindings, it's possible to return +instances wrapped in C++11 unique pointers, like so + +.. code-block:: cpp + + std::unique_ptr create_example() { return std::unique_ptr(new Example()); } + +.. code-block:: cpp + + m.def("create_example", &create_example); + +In other words, there is nothing special that needs to be done. While returning +unique pointers in this way is allowed, it is *illegal* to use them as function +arguments. For instance, the following function signature cannot be processed +by pybind11. + +.. code-block:: cpp + + void do_something_with_example(std::unique_ptr ex) { ... } + +The above signature would imply that Python needs to give up ownership of an +object that is passed to this function, which is generally not possible (for +instance, the object might be referenced elsewhere). + +std::shared_ptr +=============== + +The binding generator for classes, :class:`class_`, can be passed a template +type that denotes a special *holder* type that is used to manage references to +the object. If no such holder type template argument is given, the default for +a type named ``Type`` is ``std::unique_ptr``, which means that the object +is deallocated when Python's reference count goes to zero. + +It is possible to switch to other types of reference counting wrappers or smart +pointers, which is useful in codebases that rely on them. For instance, the +following snippet causes ``std::shared_ptr`` to be used instead. + +.. code-block:: cpp + + py::class_ /* <- holder type */> obj(m, "Example"); + +Note that any particular class can only be associated with a single holder type. + +One potential stumbling block when using holder types is that they need to be +applied consistently. Can you guess what's broken about the following binding +code? + +.. code-block:: cpp + + class Child { }; + + class Parent { + public: + Parent() : child(std::make_shared()) { } + Child *get_child() { return child.get(); } /* Hint: ** DON'T DO THIS ** */ + private: + std::shared_ptr child; + }; + + PYBIND11_MODULE(example, m) { + py::class_>(m, "Child"); + + py::class_>(m, "Parent") + .def(py::init<>()) + .def("get_child", &Parent::get_child); + } + +The following Python code will cause undefined behavior (and likely a +segmentation fault). + +.. code-block:: python + + from example import Parent + + print(Parent().get_child()) + +The problem is that ``Parent::get_child()`` returns a pointer to an instance of +``Child``, but the fact that this instance is already managed by +``std::shared_ptr<...>`` is lost when passing raw pointers. In this case, +pybind11 will create a second independent ``std::shared_ptr<...>`` that also +claims ownership of the pointer. In the end, the object will be freed **twice** +since these shared pointers have no way of knowing about each other. + +There are two ways to resolve this issue: + +1. For types that are managed by a smart pointer class, never use raw pointers + in function arguments or return values. In other words: always consistently + wrap pointers into their designated holder types (such as + ``std::shared_ptr<...>``). In this case, the signature of ``get_child()`` + should be modified as follows: + +.. code-block:: cpp + + std::shared_ptr get_child() { return child; } + +2. Adjust the definition of ``Child`` by specifying + ``std::enable_shared_from_this`` (see cppreference_ for details) as a + base class. This adds a small bit of information to ``Child`` that allows + pybind11 to realize that there is already an existing + ``std::shared_ptr<...>`` and communicate with it. In this case, the + declaration of ``Child`` should look as follows: + +.. _cppreference: http://en.cppreference.com/w/cpp/memory/enable_shared_from_this + +.. code-block:: cpp + + class Child : public std::enable_shared_from_this { }; + +.. _smart_pointers: + +Custom smart pointers +===================== + +pybind11 supports ``std::unique_ptr`` and ``std::shared_ptr`` right out of the +box. For any other custom smart pointer, transparent conversions can be enabled +using a macro invocation similar to the following. It must be declared at the +top namespace level before any binding code: + +.. code-block:: cpp + + PYBIND11_DECLARE_HOLDER_TYPE(T, SmartPtr); + +The first argument of :func:`PYBIND11_DECLARE_HOLDER_TYPE` should be a +placeholder name that is used as a template parameter of the second argument. +Thus, feel free to use any identifier, but use it consistently on both sides; +also, don't use the name of a type that already exists in your codebase. + +The macro also accepts a third optional boolean parameter that is set to false +by default. Specify + +.. code-block:: cpp + + PYBIND11_DECLARE_HOLDER_TYPE(T, SmartPtr, true); + +if ``SmartPtr`` can always be initialized from a ``T*`` pointer without the +risk of inconsistencies (such as multiple independent ``SmartPtr`` instances +believing that they are the sole owner of the ``T*`` pointer). A common +situation where ``true`` should be passed is when the ``T`` instances use +*intrusive* reference counting. + +Please take a look at the :ref:`macro_notes` before using this feature. + +By default, pybind11 assumes that your custom smart pointer has a standard +interface, i.e. provides a ``.get()`` member function to access the underlying +raw pointer. If this is not the case, pybind11's ``holder_helper`` must be +specialized: + +.. code-block:: cpp + + // Always needed for custom holder types + PYBIND11_DECLARE_HOLDER_TYPE(T, SmartPtr); + + // Only needed if the type's `.get()` goes by another name + namespace PYBIND11_NAMESPACE { namespace detail { + template + struct holder_helper> { // <-- specialization + static const T *get(const SmartPtr &p) { return p.getPointer(); } + }; + }} + +The above specialization informs pybind11 that the custom ``SmartPtr`` class +provides ``.get()`` functionality via ``.getPointer()``. + +.. seealso:: + + The file :file:`tests/test_smart_ptr.cpp` contains a complete example + that demonstrates how to work with custom reference-counting holder types + in more detail. diff --git a/extern/pybind11/docs/basics.rst b/extern/pybind11/docs/basics.rst new file mode 100644 index 000000000..e9b24c7fa --- /dev/null +++ b/extern/pybind11/docs/basics.rst @@ -0,0 +1,307 @@ +.. _basics: + +First steps +########### + +This sections demonstrates the basic features of pybind11. Before getting +started, make sure that development environment is set up to compile the +included set of test cases. + + +Compiling the test cases +======================== + +Linux/macOS +----------- + +On Linux you'll need to install the **python-dev** or **python3-dev** packages as +well as **cmake**. On macOS, the included python version works out of the box, +but **cmake** must still be installed. + +After installing the prerequisites, run + +.. code-block:: bash + + mkdir build + cd build + cmake .. + make check -j 4 + +The last line will both compile and run the tests. + +Windows +------- + +On Windows, only **Visual Studio 2017** and newer are supported. + +.. Note:: + + To use the C++17 in Visual Studio 2017 (MSVC 14.1), pybind11 requires the flag + ``/permissive-`` to be passed to the compiler `to enforce standard conformance`_. When + building with Visual Studio 2019, this is not strictly necessary, but still advised. + +.. _`to enforce standard conformance`: https://docs.microsoft.com/en-us/cpp/build/reference/permissive-standards-conformance?view=vs-2017 + +To compile and run the tests: + +.. code-block:: batch + + mkdir build + cd build + cmake .. + cmake --build . --config Release --target check + +This will create a Visual Studio project, compile and run the target, all from the +command line. + +.. Note:: + + If all tests fail, make sure that the Python binary and the testcases are compiled + for the same processor type and bitness (i.e. either **i386** or **x86_64**). You + can specify **x86_64** as the target architecture for the generated Visual Studio + project using ``cmake -A x64 ..``. + +.. seealso:: + + Advanced users who are already familiar with Boost.Python may want to skip + the tutorial and look at the test cases in the :file:`tests` directory, + which exercise all features of pybind11. + +Header and namespace conventions +================================ + +For brevity, all code examples assume that the following two lines are present: + +.. code-block:: cpp + + #include + + namespace py = pybind11; + +Some features may require additional headers, but those will be specified as needed. + +.. _simple_example: + +Creating bindings for a simple function +======================================= + +Let's start by creating Python bindings for an extremely simple function, which +adds two numbers and returns their result: + +.. code-block:: cpp + + int add(int i, int j) { + return i + j; + } + +For simplicity [#f1]_, we'll put both this function and the binding code into +a file named :file:`example.cpp` with the following contents: + +.. code-block:: cpp + + #include + + int add(int i, int j) { + return i + j; + } + + PYBIND11_MODULE(example, m) { + m.doc() = "pybind11 example plugin"; // optional module docstring + + m.def("add", &add, "A function that adds two numbers"); + } + +.. [#f1] In practice, implementation and binding code will generally be located + in separate files. + +The :func:`PYBIND11_MODULE` macro creates a function that will be called when an +``import`` statement is issued from within Python. The module name (``example``) +is given as the first macro argument (it should not be in quotes). The second +argument (``m``) defines a variable of type :class:`py::module_ ` which +is the main interface for creating bindings. The method :func:`module_::def` +generates binding code that exposes the ``add()`` function to Python. + +.. note:: + + Notice how little code was needed to expose our function to Python: all + details regarding the function's parameters and return value were + automatically inferred using template metaprogramming. This overall + approach and the used syntax are borrowed from Boost.Python, though the + underlying implementation is very different. + +pybind11 is a header-only library, hence it is not necessary to link against +any special libraries and there are no intermediate (magic) translation steps. +On Linux, the above example can be compiled using the following command: + +.. code-block:: bash + + $ c++ -O3 -Wall -shared -std=c++11 -fPIC $(python3 -m pybind11 --includes) example.cpp -o example$(python3-config --extension-suffix) + +.. note:: + + If you used :ref:`include_as_a_submodule` to get the pybind11 source, then + use ``$(python3-config --includes) -Iextern/pybind11/include`` instead of + ``$(python3 -m pybind11 --includes)`` in the above compilation, as + explained in :ref:`building_manually`. + +For more details on the required compiler flags on Linux and macOS, see +:ref:`building_manually`. For complete cross-platform compilation instructions, +refer to the :ref:`compiling` page. + +The `python_example`_ and `cmake_example`_ repositories are also a good place +to start. They are both complete project examples with cross-platform build +systems. The only difference between the two is that `python_example`_ uses +Python's ``setuptools`` to build the module, while `cmake_example`_ uses CMake +(which may be preferable for existing C++ projects). + +.. _python_example: https://github.com/pybind/python_example +.. _cmake_example: https://github.com/pybind/cmake_example + +Building the above C++ code will produce a binary module file that can be +imported to Python. Assuming that the compiled module is located in the +current directory, the following interactive Python session shows how to +load and execute the example: + +.. code-block:: pycon + + $ python + Python 3.9.10 (main, Jan 15 2022, 11:48:04) + [Clang 13.0.0 (clang-1300.0.29.3)] on darwin + Type "help", "copyright", "credits" or "license" for more information. + >>> import example + >>> example.add(1, 2) + 3 + >>> + +.. _keyword_args: + +Keyword arguments +================= + +With a simple code modification, it is possible to inform Python about the +names of the arguments ("i" and "j" in this case). + +.. code-block:: cpp + + m.def("add", &add, "A function which adds two numbers", + py::arg("i"), py::arg("j")); + +:class:`arg` is one of several special tag classes which can be used to pass +metadata into :func:`module_::def`. With this modified binding code, we can now +call the function using keyword arguments, which is a more readable alternative +particularly for functions taking many parameters: + +.. code-block:: pycon + + >>> import example + >>> example.add(i=1, j=2) + 3L + +The keyword names also appear in the function signatures within the documentation. + +.. code-block:: pycon + + >>> help(example) + + .... + + FUNCTIONS + add(...) + Signature : (i: int, j: int) -> int + + A function which adds two numbers + +A shorter notation for named arguments is also available: + +.. code-block:: cpp + + // regular notation + m.def("add1", &add, py::arg("i"), py::arg("j")); + // shorthand + using namespace pybind11::literals; + m.def("add2", &add, "i"_a, "j"_a); + +The :var:`_a` suffix forms a C++11 literal which is equivalent to :class:`arg`. +Note that the literal operator must first be made visible with the directive +``using namespace pybind11::literals``. This does not bring in anything else +from the ``pybind11`` namespace except for literals. + +.. _default_args: + +Default arguments +================= + +Suppose now that the function to be bound has default arguments, e.g.: + +.. code-block:: cpp + + int add(int i = 1, int j = 2) { + return i + j; + } + +Unfortunately, pybind11 cannot automatically extract these parameters, since they +are not part of the function's type information. However, they are simple to specify +using an extension of :class:`arg`: + +.. code-block:: cpp + + m.def("add", &add, "A function which adds two numbers", + py::arg("i") = 1, py::arg("j") = 2); + +The default values also appear within the documentation. + +.. code-block:: pycon + + >>> help(example) + + .... + + FUNCTIONS + add(...) + Signature : (i: int = 1, j: int = 2) -> int + + A function which adds two numbers + +The shorthand notation is also available for default arguments: + +.. code-block:: cpp + + // regular notation + m.def("add1", &add, py::arg("i") = 1, py::arg("j") = 2); + // shorthand + m.def("add2", &add, "i"_a=1, "j"_a=2); + +Exporting variables +=================== + +To expose a value from C++, use the ``attr`` function to register it in a +module as shown below. Built-in types and general objects (more on that later) +are automatically converted when assigned as attributes, and can be explicitly +converted using the function ``py::cast``. + +.. code-block:: cpp + + PYBIND11_MODULE(example, m) { + m.attr("the_answer") = 42; + py::object world = py::cast("World"); + m.attr("what") = world; + } + +These are then accessible from Python: + +.. code-block:: pycon + + >>> import example + >>> example.the_answer + 42 + >>> example.what + 'World' + +.. _supported_types: + +Supported data types +==================== + +A large number of data types are supported out of the box and can be used +seamlessly as functions arguments, return values or with ``py::cast`` in general. +For a full overview, see the :doc:`advanced/cast/index` section. diff --git a/extern/pybind11/docs/benchmark.py b/extern/pybind11/docs/benchmark.py new file mode 100644 index 000000000..fb49fd048 --- /dev/null +++ b/extern/pybind11/docs/benchmark.py @@ -0,0 +1,87 @@ +import datetime as dt +import os +import random + +nfns = 4 # Functions per class +nargs = 4 # Arguments per function + + +def generate_dummy_code_pybind11(nclasses=10): + decl = "" + bindings = "" + + for cl in range(nclasses): + decl += f"class cl{cl:03};\n" + decl += "\n" + + for cl in range(nclasses): + decl += f"class {cl:03} {{\n" + decl += "public:\n" + bindings += f' py::class_(m, "cl{cl:03}")\n' + for fn in range(nfns): + ret = random.randint(0, nclasses - 1) + params = [random.randint(0, nclasses - 1) for i in range(nargs)] + decl += f" cl{ret:03} *fn_{fn:03}(" + decl += ", ".join(f"cl{p:03} *" for p in params) + decl += ");\n" + bindings += f' .def("fn_{fn:03}", &cl{cl:03}::fn_{fn:03})\n' + decl += "};\n\n" + bindings += " ;\n" + + result = "#include \n\n" + result += "namespace py = pybind11;\n\n" + result += decl + "\n" + result += "PYBIND11_MODULE(example, m) {\n" + result += bindings + result += "}" + return result + + +def generate_dummy_code_boost(nclasses=10): + decl = "" + bindings = "" + + for cl in range(nclasses): + decl += f"class cl{cl:03};\n" + decl += "\n" + + for cl in range(nclasses): + decl += "class cl%03i {\n" % cl + decl += "public:\n" + bindings += f' py::class_("cl{cl:03}")\n' + for fn in range(nfns): + ret = random.randint(0, nclasses - 1) + params = [random.randint(0, nclasses - 1) for i in range(nargs)] + decl += f" cl{ret:03} *fn_{fn:03}(" + decl += ", ".join(f"cl{p:03} *" for p in params) + decl += ");\n" + bindings += f' .def("fn_{fn:03}", &cl{cl:03}::fn_{fn:03}, py::return_value_policy())\n' + decl += "};\n\n" + bindings += " ;\n" + + result = "#include \n\n" + result += "namespace py = boost::python;\n\n" + result += decl + "\n" + result += "BOOST_PYTHON_MODULE(example) {\n" + result += bindings + result += "}" + return result + + +for codegen in [generate_dummy_code_pybind11, generate_dummy_code_boost]: + print("{") + for i in range(10): + nclasses = 2**i + with open("test.cpp", "w") as f: + f.write(codegen(nclasses)) + n1 = dt.datetime.now() + os.system( + "g++ -Os -shared -rdynamic -undefined dynamic_lookup " + "-fvisibility=hidden -std=c++14 test.cpp -I include " + "-I /System/Library/Frameworks/Python.framework/Headers -o test.so" + ) + n2 = dt.datetime.now() + elapsed = (n2 - n1).total_seconds() + size = os.stat("test.so").st_size + print(" {%i, %f, %i}," % (nclasses * nfns, elapsed, size)) + print("}") diff --git a/extern/pybind11/docs/benchmark.rst b/extern/pybind11/docs/benchmark.rst new file mode 100644 index 000000000..02c2ccde7 --- /dev/null +++ b/extern/pybind11/docs/benchmark.rst @@ -0,0 +1,95 @@ +Benchmark +========= + +The following is the result of a synthetic benchmark comparing both compilation +time and module size of pybind11 against Boost.Python. A detailed report about a +Boost.Python to pybind11 conversion of a real project is available here: [#f1]_. + +.. [#f1] http://graylab.jhu.edu/RosettaCon2016/PyRosetta-4.pdf + +Setup +----- + +A python script (see the ``docs/benchmark.py`` file) was used to generate a set +of files with dummy classes whose count increases for each successive benchmark +(between 1 and 2048 classes in powers of two). Each class has four methods with +a randomly generated signature with a return value and four arguments. (There +was no particular reason for this setup other than the desire to generate many +unique function signatures whose count could be controlled in a simple way.) + +Here is an example of the binding code for one class: + +.. code-block:: cpp + + ... + class cl034 { + public: + cl279 *fn_000(cl084 *, cl057 *, cl065 *, cl042 *); + cl025 *fn_001(cl098 *, cl262 *, cl414 *, cl121 *); + cl085 *fn_002(cl445 *, cl297 *, cl145 *, cl421 *); + cl470 *fn_003(cl200 *, cl323 *, cl332 *, cl492 *); + }; + ... + + PYBIND11_MODULE(example, m) { + ... + py::class_(m, "cl034") + .def("fn_000", &cl034::fn_000) + .def("fn_001", &cl034::fn_001) + .def("fn_002", &cl034::fn_002) + .def("fn_003", &cl034::fn_003) + ... + } + +The Boost.Python version looks almost identical except that a return value +policy had to be specified as an argument to ``def()``. For both libraries, +compilation was done with + +.. code-block:: bash + + Apple LLVM version 7.0.2 (clang-700.1.81) + +and the following compilation flags + +.. code-block:: bash + + g++ -Os -shared -rdynamic -undefined dynamic_lookup -fvisibility=hidden -std=c++14 + +Compilation time +---------------- + +The following log-log plot shows how the compilation time grows for an +increasing number of class and function declarations. pybind11 includes many +fewer headers, which initially leads to shorter compilation times, but the +performance is ultimately fairly similar (pybind11 is 19.8 seconds faster for +the largest largest file with 2048 classes and a total of 8192 methods -- a +modest **1.2x** speedup relative to Boost.Python, which required 116.35 +seconds). + +.. only:: not latex + + .. image:: pybind11_vs_boost_python1.svg + +.. only:: latex + + .. image:: pybind11_vs_boost_python1.png + +Module size +----------- + +Differences between the two libraries become much more pronounced when +considering the file size of the generated Python plugin: for the largest file, +the binary generated by Boost.Python required 16.8 MiB, which was **2.17 +times** / **9.1 megabytes** larger than the output generated by pybind11. For +very small inputs, Boost.Python has an edge in the plot below -- however, note +that it stores many definitions in an external library, whose size was not +included here, hence the comparison is slightly shifted in Boost.Python's +favor. + +.. only:: not latex + + .. image:: pybind11_vs_boost_python2.svg + +.. only:: latex + + .. image:: pybind11_vs_boost_python2.png diff --git a/extern/pybind11/docs/changelog.rst b/extern/pybind11/docs/changelog.rst new file mode 100644 index 000000000..d2285f237 --- /dev/null +++ b/extern/pybind11/docs/changelog.rst @@ -0,0 +1,3006 @@ +.. _changelog: + +Changelog +######### + +Starting with version 1.8.0, pybind11 releases use a `semantic versioning +`_ policy. + +Changes will be added here periodically from the "Suggested changelog entry" +block in pull request descriptions. + + +IN DEVELOPMENT +-------------- + +Changes will be summarized here periodically. + +Version 2.12.0 (March 27, 2025) +------------------------------- + +New Features: + +* ``pybind11`` now supports compiling for + `NumPy 2 `_. Most + code shouldn't change (see :ref:`upgrade-guide-2.12` for details). However, + if you experience issues you can define ``PYBIND11_NUMPY_1_ONLY`` to disable + the new support for now, but this will be removed in the future. + `#5050 `_ + +* ``pybind11/gil_safe_call_once.h`` was added (it needs to be included + explicitly). The primary use case is GIL-safe initialization of C++ + ``static`` variables. + `#4877 `_ + +* Support move-only iterators in ``py::make_iterator``, + ``py::make_key_iterator``, ``py::make_value_iterator``. + `#4834 `_ + +* Two simple ``py::set_error()`` functions were added and the documentation was + updated accordingly. In particular, ``py::exception<>::operator()`` was + deprecated (use one of the new functions instead). The documentation for + ``py::exception<>`` was further updated to not suggest code that may result + in undefined behavior. + `#4772 `_ + +Bug fixes: + +* Removes potential for Undefined Behavior during process teardown. + `#4897 `_ + +* Improve compatibility with the nvcc compiler (especially CUDA 12.1/12.2). + `#4893 `_ + +* ``pybind11/numpy.h`` now imports NumPy's ``multiarray`` and ``_internal`` + submodules with paths depending on the installed version of NumPy (for + compatibility with NumPy 2). + `#4857 `_ + +* Builtins collections names in docstrings are now consistently rendered in + lowercase (list, set, dict, tuple), in accordance with PEP 585. + `#4833 `_ + +* Added ``py::typing::Iterator``, ``py::typing::Iterable``. + `#4832 `_ + +* Render ``py::function`` as ``Callable`` in docstring. + `#4829 `_ + +* Also bump ``PYBIND11_INTERNALS_VERSION`` for MSVC, which unlocks two new + features without creating additional incompatibilities. + `#4819 `_ + +* Guard against crashes/corruptions caused by modules built with different MSVC + versions. + `#4779 `_ + +* A long-standing bug in the handling of Python multiple inheritance was fixed. + See PR #4762 for the rather complex details. + `#4762 `_ + +* Fix ``bind_map`` with ``using`` declarations. + `#4952 `_ + +* Qualify ``py::detail::concat`` usage to avoid ADL selecting one from + somewhere else, such as modernjson's concat. + `#4955 `_ + +* Use new PyCode API on Python 3.12+. + `#4916 `_ + +* Minor cleanup from warnings reported by Clazy. + `#4988 `_ + +* Remove typing and duplicate ``class_`` for ``KeysView``/``ValuesView``/``ItemsView``. + `#4985 `_ + +* Use ``PyObject_VisitManagedDict()`` and ``PyObject_ClearManagedDict()`` on Python 3.13 and newer. + `#4973 `_ + +* Update ``make_static_property_type()`` to make it compatible with Python 3.13. + `#4971 `_ + +.. fix(types) + +* Render typed iterators for ``make_iterator``, ``make_key_iterator``, + ``make_value_iterator``. + `#4876 `_ + +* Add several missing type name specializations. + `#5073 `_ + +* Change docstring render for ``py::buffer``, ``py::sequence`` and + ``py::handle`` (to ``Buffer``, ``Sequence``, ``Any``). + `#4831 `_ + +* Fixed ``base_enum.__str__`` docstring. + `#4827 `_ + +* Enforce single line docstring signatures. + `#4735 `_ + +* Special 'typed' wrappers now available in ``typing.h`` to annotate tuple, dict, + list, set, and function. + `#4259 `_ + +* Create ``handle_type_name`` specialization to type-hint variable length tuples. + `#5051 `_ + +.. fix(build) + +* Setting ``PYBIND11_FINDPYTHON`` to OFF will force the old FindPythonLibs mechanism to be used. + `#5042 `_ + +* Skip empty ``PYBIND11_PYTHON_EXECUTABLE_LAST`` for the first cmake run. + `#4856 `_ + +* Fix FindPython mode exports & avoid ``pkg_resources`` if + ``importlib.metadata`` available. + `#4941 `_ + +* ``Python_ADDITIONAL_VERSIONS`` (classic search) now includes 3.12. + `#4909 `_ + +* ``pybind11.pc`` is now relocatable by default as long as install destinations + are not absolute paths. + `#4830 `_ + +* Correctly detect CMake FindPython removal when used as a subdirectory. + `#4806 `_ + +* Don't require the libs component on CMake 3.18+ when using + PYBIND11_FINDPYTHON (fixes manylinux builds). + `#4805 `_ + +* ``pybind11_strip`` is no longer automatically applied when + ``CMAKE_BUILD_TYPE`` is unset. + `#4780 `_ + +* Support ``DEBUG_POSFIX`` correctly for debug builds. + `#4761 `_ + +* Hardcode lto/thin lto for Emscripten cross-compiles. + `#4642 `_ + +* Upgrade maximum supported CMake version to 3.27 to fix CMP0148 warnings. + `#4786 `_ + +Documentation: + +* Small fix to grammar in ``functions.rst``. + `#4791 `_ + +* Remove upper bound in example pyproject.toml for setuptools. + `#4774 `_ + +CI: + +* CI: Update NVHPC to 23.5 and Ubuntu 20.04. + `#4764 `_ + +* Test on PyPy 3.10. + `#4714 `_ + +Other: + +* Use Ruff formatter instead of Black. + `#4912 `_ + +* An ``assert()`` was added to help Coverty avoid generating a false positive. + `#4817 `_ + + +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) +----------------------------- + +Removed support for Python 2.7, Python 3.5, and MSVC 2015. Support for MSVC +2017 is limited due to availability of CI runners; we highly recommend MSVC +2019 or 2022 be used. Initial support added for Python 3.11. + +New features: + +* ``py::anyset`` & ``py::frozenset`` were added, with copying (cast) to + ``std::set`` (similar to ``set``). + `#3901 `_ + +* Support bytearray casting to string. + `#3707 `_ + +* ``type_caster`` was added. ``std::monostate`` is a tag type + that allows ``std::variant`` to act as an optional, or allows default + construction of a ``std::variant`` holding a non-default constructible type. + `#3818 `_ + +* ``pybind11::capsule::set_name`` added to mutate the name of the capsule instance. + `#3866 `_ + +* NumPy: dtype constructor from type number added, accessors corresponding to + Python API ``dtype.num``, ``dtype.byteorder``, ``dtype.flags`` and + ``dtype.alignment`` added. + `#3868 `_ + + +Changes: + +* Python 3.6 is now the minimum supported version. + `#3688 `_ + `#3719 `_ + +* The minimum version for MSVC is now 2017. + `#3722 `_ + +* Fix issues with CPython 3.11 betas and add to supported test matrix. + `#3923 `_ + +* ``error_already_set`` is now safer and more performant, especially for + exceptions with long tracebacks, by delaying computation. + `#1895 `_ + +* Improve exception handling in python ``str`` bindings. + `#3826 `_ + +* The bindings for capsules now have more consistent exception handling. + `#3825 `_ + +* ``PYBIND11_OBJECT_CVT`` and ``PYBIND11_OBJECT_CVT_DEFAULT`` macro can now be + used to define classes in namespaces other than pybind11. + `#3797 `_ + +* Error printing code now uses ``PYBIND11_DETAILED_ERROR_MESSAGES`` instead of + requiring ``NDEBUG``, allowing use with release builds if desired. + `#3913 `_ + +* Implicit conversion of the literal ``0`` to ``pybind11::handle`` is now disabled. + `#4008 `_ + + +Bug fixes: + +* Fix exception handling when ``pybind11::weakref()`` fails. + `#3739 `_ + +* ``module_::def_submodule`` was missing proper error handling. This is fixed now. + `#3973 `_ + +* The behavior or ``error_already_set`` was made safer and the highly opaque + "Unknown internal error occurred" message was replaced with a more helpful + message. + `#3982 `_ + +* ``error_already_set::what()`` now handles non-normalized exceptions correctly. + `#3971 `_ + +* Support older C++ compilers where filesystem is not yet part of the standard + library and is instead included in ``std::experimental::filesystem``. + `#3840 `_ + +* Fix ``-Wfree-nonheap-object`` warnings produced by GCC by avoiding returning + pointers to static objects with ``return_value_policy::take_ownership``. + `#3946 `_ + +* Fix cast from pytype rvalue to another pytype. + `#3949 `_ + +* Ensure proper behavior when garbage collecting classes with dynamic attributes in Python >=3.9. + `#4051 `_ + +* A couple long-standing ``PYBIND11_NAMESPACE`` + ``__attribute__((visibility("hidden")))`` inconsistencies are now fixed + (affects only unusual environments). + `#4043 `_ + +* ``pybind11::detail::get_internals()`` is now resilient to in-flight Python + exceptions. + `#3981 `_ + +* Arrays with a dimension of size 0 are now properly converted to dynamic Eigen + matrices (more common in NumPy 1.23). + `#4038 `_ + +* Avoid catching unrelated errors when importing NumPy. + `#3974 `_ + +Performance and style: + +* Added an accessor overload of ``(object &&key)`` to reference steal the + object when using python types as keys. This prevents unnecessary reference + count overhead for attr, dictionary, tuple, and sequence look ups. Added + additional regression tests. Fixed a performance bug the caused accessor + assignments to potentially perform unnecessary copies. + `#3970 `_ + +* Perfect forward all args of ``make_iterator``. + `#3980 `_ + +* Avoid potential bug in pycapsule destructor by adding an ``error_guard`` to + one of the dtors. + `#3958 `_ + +* Optimize dictionary access in ``strip_padding`` for numpy. + `#3994 `_ + +* ``stl_bind.h`` bindings now take slice args as a const-ref. + `#3852 `_ + +* Made slice constructor more consistent, and improve performance of some + casters by allowing reference stealing. + `#3845 `_ + +* Change numpy dtype from_args method to use const ref. + `#3878 `_ + +* Follow rule of three to ensure ``PyErr_Restore`` is called only once. + `#3872 `_ + +* Added missing perfect forwarding for ``make_iterator`` functions. + `#3860 `_ + +* Optimize c++ to python function casting by using the rvalue caster. + `#3966 `_ + +* Optimize Eigen sparse matrix casting by removing unnecessary temporary. + `#4064 `_ + +* Avoid potential implicit copy/assignment constructors causing double free in + ``strdup_gaurd``. + `#3905 `_ + +* Enable clang-tidy checks ``misc-definitions-in-headers``, + ``modernize-loop-convert``, and ``modernize-use-nullptr``. + `#3881 `_ + `#3988 `_ + + +Build system improvements: + +* CMake: Fix file extension on Windows with cp36 and cp37 using FindPython. + `#3919 `_ + +* CMake: Support multiple Python targets (such as on vcpkg). + `#3948 `_ + +* CMake: Fix issue with NVCC on Windows. + `#3947 `_ + +* CMake: Drop the bitness check on cross compiles (like targeting WebAssembly + via Emscripten). + `#3959 `_ + +* Add MSVC builds in debug mode to CI. + `#3784 `_ + +* MSVC 2022 C++20 coverage was added to GitHub Actions, including Eigen. + `#3732 `_, + `#3741 `_ + + +Backend and tidying up: + +* New theme for the documentation. + `#3109 `_ + +* Remove idioms in code comments. Use more inclusive language. + `#3809 `_ + +* ``#include `` was removed from the ``pybind11/stl.h`` header. Your + project may break if it has a transitive dependency on this include. The fix + is to "Include What You Use". + `#3928 `_ + +* Avoid ``setup.py `` usage in internal tests. + `#3734 `_ + + +Version 2.9.2 (Mar 29, 2022) +---------------------------- + +Changes: + +* Enum now has an ``__index__`` method on Python <3.8 too. + `#3700 `_ + +* Local internals are now cleared after finalizing the interpreter. + `#3744 `_ + +Bug fixes: + +* Better support for Python 3.11 alphas. + `#3694 `_ + +* ``PYBIND11_TYPE_CASTER`` now uses fully qualified symbols, so it can be used + outside of ``pybind11::detail``. + `#3758 `_ + +* Some fixes for PyPy 3.9. + `#3768 `_ + +* Fixed a potential memleak in PyPy in ``get_type_override``. + `#3774 `_ + +* Fix usage of ``VISIBILITY_INLINES_HIDDEN``. + `#3721 `_ + + +Build system improvements: + +* Uses ``sysconfig`` module to determine installation locations on Python >= + 3.10, instead of ``distutils`` which has been deprecated. + `#3764 `_ + +* Support Catch 2.13.5+ (supporting GLIBC 2.34+). + `#3679 `_ + +* Fix test failures with numpy 1.22 by ignoring whitespace when comparing + ``str()`` of dtypes. + `#3682 `_ + + +Backend and tidying up: + +* clang-tidy: added ``readability-qualified-auto``, + ``readability-braces-around-statements``, + ``cppcoreguidelines-prefer-member-initializer``, + ``clang-analyzer-optin.performance.Padding``, + ``cppcoreguidelines-pro-type-static-cast-downcast``, and + ``readability-inconsistent-declaration-parameter-name``. + `#3702 `_, + `#3699 `_, + `#3716 `_, + `#3709 `_ + +* clang-format was added to the pre-commit actions, and the entire code base + automatically reformatted (after several iterations preparing for this leap). + `#3713 `_ + + +Version 2.9.1 (Feb 2, 2022) +--------------------------- + +Changes: + +* If possible, attach Python exception with ``py::raise_from`` to ``TypeError`` + when casting from C++ to Python. This will give additional info if Python + exceptions occur in the caster. Adds a test case of trying to convert a set + from C++ to Python when the hash function is not defined in Python. + `#3605 `_ + +* Add a mapping of C++11 nested exceptions to their Python exception + equivalent using ``py::raise_from``. This attaches the nested exceptions in + Python using the ``__cause__`` field. + `#3608 `_ + +* Propagate Python exception traceback using ``raise_from`` if a pybind11 + function runs out of overloads. + `#3671 `_ + +* ``py::multiple_inheritance`` is now only needed when C++ bases are hidden + from pybind11. + `#3650 `_ and + `#3659 `_ + + +Bug fixes: + +* Remove a boolean cast in ``numpy.h`` that causes MSVC C4800 warnings when + compiling against Python 3.10 or newer. + `#3669 `_ + +* Render ``py::bool_`` and ``py::float_`` as ``bool`` and ``float`` + respectively. + `#3622 `_ + +Build system improvements: + +* Fix CMake extension suffix computation on Python 3.10+. + `#3663 `_ + +* Allow ``CMAKE_ARGS`` to override CMake args in pybind11's own ``setup.py``. + `#3577 `_ + +* Remove a few deprecated c-headers. + `#3610 `_ + +* More uniform handling of test targets. + `#3590 `_ + +* Add clang-tidy readability check to catch potentially swapped function args. + `#3611 `_ + + +Version 2.9.0 (Dec 28, 2021) +---------------------------- + +This is the last version to support Python 2.7 and 3.5. + +New Features: + +* Allow ``py::args`` to be followed by other arguments; the remaining arguments + are implicitly keyword-only, as if a ``py::kw_only{}`` annotation had been + used. + `#3402 `_ + +Changes: + +* Make str/bytes/memoryview more interoperable with ``std::string_view``. + `#3521 `_ + +* Replace ``_`` with ``const_name`` in internals, avoid defining ``pybind::_`` + if ``_`` defined as macro (common gettext usage) + `#3423 `_ + + +Bug fixes: + +* Fix a rare warning about extra copy in an Eigen constructor. + `#3486 `_ + +* Fix caching of the C++ overrides. + `#3465 `_ + +* Add missing ``std::forward`` calls to some ``cpp_function`` overloads. + `#3443 `_ + +* Support PyPy 7.3.7 and the PyPy3.8 beta. Test python-3.11 on PRs with the + ``python dev`` label. + `#3419 `_ + +* Replace usage of deprecated ``Eigen::MappedSparseMatrix`` with + ``Eigen::Map>`` for Eigen 3.3+. + `#3499 `_ + +* Tweaks to support Microsoft Visual Studio 2022. + `#3497 `_ + +Build system improvements: + +* Nicer CMake printout and IDE organisation for pybind11's own tests. + `#3479 `_ + +* CMake: report version type as part of the version string to avoid a spurious + space in the package status message. + `#3472 `_ + +* Flags starting with ``-g`` in ``$CFLAGS`` and ``$CPPFLAGS`` are no longer + overridden by ``.Pybind11Extension``. + `#3436 `_ + +* Ensure ThreadPool is closed in ``setup_helpers``. + `#3548 `_ + +* Avoid LTS on ``mips64`` and ``ppc64le`` (reported broken). + `#3557 `_ + + +v2.8.1 (Oct 27, 2021) +--------------------- + +Changes and additions: + +* The simple namespace creation shortcut added in 2.8.0 was deprecated due to + usage of CPython internal API, and will be removed soon. Use + ``py::module_::import("types").attr("SimpleNamespace")``. + `#3374 `_ + +* Add C++ Exception type to throw and catch ``AttributeError``. Useful for + defining custom ``__setattr__`` and ``__getattr__`` methods. + `#3387 `_ + +Fixes: + +* Fixed the potential for dangling references when using properties with + ``std::optional`` types. + `#3376 `_ + +* Modernize usage of ``PyCodeObject`` on Python 3.9+ (moving toward support for + Python 3.11a1) + `#3368 `_ + +* A long-standing bug in ``eigen.h`` was fixed (originally PR #3343). The bug + was unmasked by newly added ``static_assert``'s in the Eigen 3.4.0 release. + `#3352 `_ + +* Support multiple raw inclusion of CMake helper files (Conan.io does this for + multi-config generators). + `#3420 `_ + +* Fix harmless warning on upcoming CMake 3.22. + `#3368 `_ + +* Fix 2.8.0 regression with MSVC 2017 + C++17 mode + Python 3. + `#3407 `_ + +* Fix 2.8.0 regression that caused undefined behavior (typically + segfaults) in ``make_key_iterator``/``make_value_iterator`` if dereferencing + the iterator returned a temporary value instead of a reference. + `#3348 `_ + + +v2.8.0 (Oct 4, 2021) +-------------------- + +New features: + +* Added ``py::raise_from`` to enable chaining exceptions. + `#3215 `_ + +* Allow exception translators to be optionally registered local to a module + instead of applying globally across all pybind11 modules. Use + ``register_local_exception_translator(ExceptionTranslator&& translator)`` + instead of ``register_exception_translator(ExceptionTranslator&& + translator)`` to keep your exception remapping code local to the module. + `#2650 `_ + +* Add ``make_simple_namespace`` function for instantiating Python + ``SimpleNamespace`` objects. **Deprecated in 2.8.1.** + `#2840 `_ + +* ``pybind11::scoped_interpreter`` and ``initialize_interpreter`` have new + arguments to allow ``sys.argv`` initialization. + `#2341 `_ + +* Allow Python builtins to be used as callbacks in CPython. + `#1413 `_ + +* Added ``view`` to view arrays with a different datatype. + `#987 `_ + +* Implemented ``reshape`` on arrays. + `#984 `_ + +* Enable defining custom ``__new__`` methods on classes by fixing bug + preventing overriding methods if they have non-pybind11 siblings. + `#3265 `_ + +* Add ``make_value_iterator()``, and fix ``make_key_iterator()`` to return + references instead of copies. + `#3293 `_ + +* Improve the classes generated by ``bind_map``: `#3310 `_ + + * Change ``.items`` from an iterator to a dictionary view. + * Add ``.keys`` and ``.values`` (both dictionary views). + * Allow ``__contains__`` to take any object. + +* ``pybind11::custom_type_setup`` was added, for customizing the + ``PyHeapTypeObject`` corresponding to a class, which may be useful for + enabling garbage collection support, among other things. + `#3287 `_ + + +Changes: + +* Set ``__file__`` constant when running ``eval_file`` in an embedded interpreter. + `#3233 `_ + +* Python objects and (C++17) ``std::optional`` now accepted in ``py::slice`` + constructor. + `#1101 `_ + +* The pybind11 proxy types ``str``, ``bytes``, ``bytearray``, ``tuple``, + ``list`` now consistently support passing ``ssize_t`` values for sizes and + indexes. Previously, only ``size_t`` was accepted in several interfaces. + `#3219 `_ + +* Avoid evaluating ``PYBIND11_TLS_REPLACE_VALUE`` arguments more than once. + `#3290 `_ + +Fixes: + +* Bug fix: enum value's ``__int__`` returning non-int when underlying type is + bool or of char type. + `#1334 `_ + +* Fixes bug in setting error state in Capsule's pointer methods. + `#3261 `_ + +* A long-standing memory leak in ``py::cpp_function::initialize`` was fixed. + `#3229 `_ + +* Fixes thread safety for some ``pybind11::type_caster`` which require lifetime + extension, such as for ``std::string_view``. + `#3237 `_ + +* Restore compatibility with gcc 4.8.4 as distributed by ubuntu-trusty, linuxmint-17. + `#3270 `_ + + +Build system improvements: + +* Fix regression in CMake Python package config: improper use of absolute path. + `#3144 `_ + +* Cached Python version information could become stale when CMake was re-run + with a different Python version. The build system now detects this and + updates this information. + `#3299 `_ + +* Specified UTF8-encoding in setup.py calls of open(). + `#3137 `_ + +* Fix a harmless warning from CMake 3.21 with the classic Python discovery. + `#3220 `_ + +* Eigen repo and version can now be specified as cmake options. + `#3324 `_ + + +Backend and tidying up: + +* Reduced thread-local storage required for keeping alive temporary data for + type conversion to one key per ABI version, rather than one key per extension + module. This makes the total thread-local storage required by pybind11 2 + keys per ABI version. + `#3275 `_ + +* Optimize NumPy array construction with additional moves. + `#3183 `_ + +* Conversion to ``std::string`` and ``std::string_view`` now avoids making an + extra copy of the data on Python >= 3.3. + `#3257 `_ + +* Remove const modifier from certain C++ methods on Python collections + (``list``, ``set``, ``dict``) such as (``clear()``, ``append()``, + ``insert()``, etc...) and annotated them with ``py-non-const``. + +* Enable readability ``clang-tidy-const-return`` and remove useless consts. + `#3254 `_ + `#3194 `_ + +* The clang-tidy ``google-explicit-constructor`` option was enabled. + `#3250 `_ + +* Mark a pytype move constructor as noexcept (perf). + `#3236 `_ + +* Enable clang-tidy check to guard against inheritance slicing. + `#3210 `_ + +* Legacy warning suppression pragma were removed from eigen.h. On Unix + platforms, please use -isystem for Eigen include directories, to suppress + compiler warnings originating from Eigen headers. Note that CMake does this + by default. No adjustments are needed for Windows. + `#3198 `_ + +* Format pybind11 with isort consistent ordering of imports + `#3195 `_ + +* The warnings-suppression "pragma clamp" at the top/bottom of pybind11 was + removed, clearing the path to refactoring and IWYU cleanup. + `#3186 `_ + +* Enable most bugprone checks in clang-tidy and fix the found potential bugs + and poor coding styles. + `#3166 `_ + +* Add ``clang-tidy-readability`` rules to make boolean casts explicit improving + code readability. Also enabled other misc and readability clang-tidy checks. + `#3148 `_ + +* Move object in ``.pop()`` for list. + `#3116 `_ + + + + +v2.7.1 (Aug 3, 2021) +--------------------- + +Minor missing functionality added: + +* Allow Python builtins to be used as callbacks in CPython. + `#1413 `_ + +Bug fixes: + +* Fix regression in CMake Python package config: improper use of absolute path. + `#3144 `_ + +* Fix Mingw64 and add to the CI testing matrix. + `#3132 `_ + +* Specified UTF8-encoding in setup.py calls of open(). + `#3137 `_ + +* Add clang-tidy-readability rules to make boolean casts explicit improving + code readability. Also enabled other misc and readability clang-tidy checks. + `#3148 `_ + +* Move object in ``.pop()`` for list. + `#3116 `_ + +Backend and tidying up: + +* Removed and fixed warning suppressions. + `#3127 `_ + `#3129 `_ + `#3135 `_ + `#3141 `_ + `#3142 `_ + `#3150 `_ + `#3152 `_ + `#3160 `_ + `#3161 `_ + + +v2.7.0 (Jul 16, 2021) +--------------------- + +New features: + +* Enable ``py::implicitly_convertible`` for + ``py::class_``-wrapped types. + `#3059 `_ + +* Allow function pointer extraction from overloaded functions. + `#2944 `_ + +* NumPy: added ``.char_()`` to type which gives the NumPy public ``char`` + result, which also distinguishes types by bit length (unlike ``.kind()``). + `#2864 `_ + +* Add ``pybind11::bytearray`` to manipulate ``bytearray`` similar to ``bytes``. + `#2799 `_ + +* ``pybind11/stl/filesystem.h`` registers a type caster that, on C++17/Python + 3.6+, converts ``std::filesystem::path`` to ``pathlib.Path`` and any + ``os.PathLike`` to ``std::filesystem::path``. + `#2730 `_ + +* A ``PYBIND11_VERSION_HEX`` define was added, similar to ``PY_VERSION_HEX``. + `#3120 `_ + + + +Changes: + +* ``py::str`` changed to exclusively hold ``PyUnicodeObject``. Previously + ``py::str`` could also hold ``bytes``, which is probably surprising, was + never documented, and can mask bugs (e.g. accidental use of ``py::str`` + instead of ``py::bytes``). + `#2409 `_ + +* Add a safety guard to ensure that the Python GIL is held when C++ calls back + into Python via ``object_api<>::operator()`` (e.g. ``py::function`` + ``__call__``). (This feature is available for Python 3.6+ only.) + `#2919 `_ + +* Catch a missing ``self`` argument in calls to ``__init__()``. + `#2914 `_ + +* Use ``std::string_view`` if available to avoid a copy when passing an object + to a ``std::ostream``. + `#3042 `_ + +* An important warning about thread safety was added to the ``iostream.h`` + documentation; attempts to make ``py::scoped_ostream_redirect`` thread safe + have been removed, as it was only partially effective. + `#2995 `_ + + +Fixes: + +* Performance: avoid unnecessary strlen calls. + `#3058 `_ + +* Fix auto-generated documentation string when using ``const T`` in + ``pyarray_t``. + `#3020 `_ + +* Unify error messages thrown by ``simple_collector``/``unpacking_collector``. + `#3013 `_ + +* ``pybind11::builtin_exception`` is now explicitly exported, which means the + types included/defined in different modules are identical, and exceptions + raised in different modules can be caught correctly. The documentation was + updated to explain that custom exceptions that are used across module + boundaries need to be explicitly exported as well. + `#2999 `_ + +* Fixed exception when printing UTF-8 to a ``scoped_ostream_redirect``. + `#2982 `_ + +* Pickle support enhancement: ``setstate`` implementation will attempt to + ``setattr`` ``__dict__`` only if the unpickled ``dict`` object is not empty, + to not force use of ``py::dynamic_attr()`` unnecessarily. + `#2972 `_ + +* Allow negative timedelta values to roundtrip. + `#2870 `_ + +* Fix unchecked errors could potentially swallow signals/other exceptions. + `#2863 `_ + +* Add null pointer check with ``std::localtime``. + `#2846 `_ + +* Fix the ``weakref`` constructor from ``py::object`` to create a new + ``weakref`` on conversion. + `#2832 `_ + +* Avoid relying on exceptions in C++17 when getting a ``shared_ptr`` holder + from a ``shared_from_this`` class. + `#2819 `_ + +* Allow the codec's exception to be raised instead of :code:`RuntimeError` when + casting from :code:`py::str` to :code:`std::string`. + `#2903 `_ + + +Build system improvements: + +* In ``setup_helpers.py``, test for platforms that have some multiprocessing + features but lack semaphores, which ``ParallelCompile`` requires. + `#3043 `_ + +* Fix ``pybind11_INCLUDE_DIR`` in case ``CMAKE_INSTALL_INCLUDEDIR`` is + absolute. + `#3005 `_ + +* Fix bug not respecting ``WITH_SOABI`` or ``WITHOUT_SOABI`` to CMake. + `#2938 `_ + +* Fix the default ``Pybind11Extension`` compilation flags with a Mingw64 python. + `#2921 `_ + +* Clang on Windows: do not pass ``/MP`` (ignored flag). + `#2824 `_ + +* ``pybind11.setup_helpers.intree_extensions`` can be used to generate + ``Pybind11Extension`` instances from cpp files placed in the Python package + source tree. + `#2831 `_ + +Backend and tidying up: + +* Enable clang-tidy performance, readability, and modernization checks + throughout the codebase to enforce best coding practices. + `#3046 `_, + `#3049 `_, + `#3051 `_, + `#3052 `_, + `#3080 `_, and + `#3094 `_ + + +* Checks for common misspellings were added to the pre-commit hooks. + `#3076 `_ + +* Changed ``Werror`` to stricter ``Werror-all`` for Intel compiler and fixed + minor issues. + `#2948 `_ + +* Fixed compilation with GCC < 5 when the user defines ``_GLIBCXX_USE_CXX11_ABI``. + `#2956 `_ + +* Added nox support for easier local testing and linting of contributions. + `#3101 `_ and + `#3121 `_ + +* Avoid RTD style issue with docutils 0.17+. + `#3119 `_ + +* Support pipx run, such as ``pipx run pybind11 --include`` for a quick compile. + `#3117 `_ + + + +v2.6.2 (Jan 26, 2021) +--------------------- + +Minor missing functionality added: + +* enum: add missing Enum.value property. + `#2739 `_ + +* Allow thread termination to be avoided during shutdown for CPython 3.7+ via + ``.disarm`` for ``gil_scoped_acquire``/``gil_scoped_release``. + `#2657 `_ + +Fixed or improved behavior in a few special cases: + +* Fix bug where the constructor of ``object`` subclasses would not throw on + being passed a Python object of the wrong type. + `#2701 `_ + +* The ``type_caster`` for integers does not convert Python objects with + ``__int__`` anymore with ``noconvert`` or during the first round of trying + overloads. + `#2698 `_ + +* When casting to a C++ integer, ``__index__`` is always called and not + considered as conversion, consistent with Python 3.8+. + `#2801 `_ + +Build improvements: + +* Setup helpers: ``extra_compile_args`` and ``extra_link_args`` automatically set by + Pybind11Extension are now prepended, which allows them to be overridden + by user-set ``extra_compile_args`` and ``extra_link_args``. + `#2808 `_ + +* Setup helpers: Don't trigger unused parameter warning. + `#2735 `_ + +* CMake: Support running with ``--warn-uninitialized`` active. + `#2806 `_ + +* CMake: Avoid error if included from two submodule directories. + `#2804 `_ + +* CMake: Fix ``STATIC`` / ``SHARED`` being ignored in FindPython mode. + `#2796 `_ + +* CMake: Respect the setting for ``CMAKE_CXX_VISIBILITY_PRESET`` if defined. + `#2793 `_ + +* CMake: Fix issue with FindPython2/FindPython3 not working with ``pybind11::embed``. + `#2662 `_ + +* CMake: mixing local and installed pybind11's would prioritize the installed + one over the local one (regression in 2.6.0). + `#2716 `_ + + +Bug fixes: + +* Fixed segfault in multithreaded environments when using + ``scoped_ostream_redirect``. + `#2675 `_ + +* Leave docstring unset when all docstring-related options are disabled, rather + than set an empty string. + `#2745 `_ + +* The module key in builtins that pybind11 uses to store its internals changed + from std::string to a python str type (more natural on Python 2, no change on + Python 3). + `#2814 `_ + +* Fixed assertion error related to unhandled (later overwritten) exception in + CPython 3.8 and 3.9 debug builds. + `#2685 `_ + +* Fix ``py::gil_scoped_acquire`` assert with CPython 3.9 debug build. + `#2683 `_ + +* Fix issue with a test failing on pytest 6.2. + `#2741 `_ + +Warning fixes: + +* Fix warning modifying constructor parameter 'flag' that shadows a field of + 'set_flag' ``[-Wshadow-field-in-constructor-modified]``. + `#2780 `_ + +* Suppressed some deprecation warnings about old-style + ``__init__``/``__setstate__`` in the tests. + `#2759 `_ + +Valgrind work: + +* Fix invalid access when calling a pybind11 ``__init__`` on a non-pybind11 + class instance. + `#2755 `_ + +* Fixed various minor memory leaks in pybind11's test suite. + `#2758 `_ + +* Resolved memory leak in cpp_function initialization when exceptions occurred. + `#2756 `_ + +* Added a Valgrind build, checking for leaks and memory-related UB, to CI. + `#2746 `_ + +Compiler support: + +* Intel compiler was not activating C++14 support due to a broken define. + `#2679 `_ + +* Support ICC and NVIDIA HPC SDK in C++17 mode. + `#2729 `_ + +* Support Intel OneAPI compiler (ICC 20.2) and add to CI. + `#2573 `_ + + + +v2.6.1 (Nov 11, 2020) +--------------------- + +* ``py::exec``, ``py::eval``, and ``py::eval_file`` now add the builtins module + as ``"__builtins__"`` to their ``globals`` argument, better matching ``exec`` + and ``eval`` in pure Python. + `#2616 `_ + +* ``setup_helpers`` will no longer set a minimum macOS version higher than the + current version. + `#2622 `_ + +* Allow deleting static properties. + `#2629 `_ + +* Seal a leak in ``def_buffer``, cleaning up the ``capture`` object after the + ``class_`` object goes out of scope. + `#2634 `_ + +* ``pybind11_INCLUDE_DIRS`` was incorrect, potentially causing a regression if + it was expected to include ``PYTHON_INCLUDE_DIRS`` (please use targets + instead). + `#2636 `_ + +* Added parameter names to the ``py::enum_`` constructor and methods, avoiding + ``arg0`` in the generated docstrings. + `#2637 `_ + +* Added ``needs_recompile`` optional function to the ``ParallelCompiler`` + helper, to allow a recompile to be skipped based on a user-defined function. + `#2643 `_ + + +v2.6.0 (Oct 21, 2020) +--------------------- + +See :ref:`upgrade-guide-2.6` for help upgrading to the new version. + +New features: + +* Keyword-only arguments supported in Python 2 or 3 with ``py::kw_only()``. + `#2100 `_ + +* Positional-only arguments supported in Python 2 or 3 with ``py::pos_only()``. + `#2459 `_ + +* ``py::is_final()`` class modifier to block subclassing (CPython only). + `#2151 `_ + +* Added ``py::prepend()``, allowing a function to be placed at the beginning of + the overload chain. + `#1131 `_ + +* Access to the type object now provided with ``py::type::of()`` and + ``py::type::of(h)``. + `#2364 `_ + +* Perfect forwarding support for methods. + `#2048 `_ + +* Added ``py::error_already_set::discard_as_unraisable()``. + `#2372 `_ + +* ``py::hash`` is now public. + `#2217 `_ + +* ``py::class_`` is now supported. Note that writing to one data + member of the union and reading another (type punning) is UB in C++. Thus + pybind11-bound enums should never be used for such conversions. + `#2320 `_. + +* Classes now check local scope when registering members, allowing a subclass + to have a member with the same name as a parent (such as an enum). + `#2335 `_ + +Code correctness features: + +* Error now thrown when ``__init__`` is forgotten on subclasses. + `#2152 `_ + +* Throw error if conversion to a pybind11 type if the Python object isn't a + valid instance of that type, such as ``py::bytes(o)`` when ``py::object o`` + isn't a bytes instance. + `#2349 `_ + +* Throw if conversion to ``str`` fails. + `#2477 `_ + + +API changes: + +* ``py::module`` was renamed ``py::module_`` to avoid issues with C++20 when + used unqualified, but an alias ``py::module`` is provided for backward + compatibility. + `#2489 `_ + +* Public constructors for ``py::module_`` have been deprecated; please use + ``pybind11::module_::create_extension_module`` if you were using the public + constructor (fairly rare after ``PYBIND11_MODULE`` was introduced). + `#2552 `_ + +* ``PYBIND11_OVERLOAD*`` macros and ``get_overload`` function replaced by + correctly-named ``PYBIND11_OVERRIDE*`` and ``get_override``, fixing + inconsistencies in the presence of a closing ``;`` in these macros. + ``get_type_overload`` is deprecated. + `#2325 `_ + +Packaging / building improvements: + +* The Python package was reworked to be more powerful and useful. + `#2433 `_ + + * :ref:`build-setuptools` is easier thanks to a new + ``pybind11.setup_helpers`` module, which provides utilities to use + setuptools with pybind11. It can be used via PEP 518, ``setup_requires``, + or by directly importing or copying ``setup_helpers.py`` into your project. + + * CMake configuration files are now included in the Python package. Use + ``pybind11.get_cmake_dir()`` or ``python -m pybind11 --cmakedir`` to get + the directory with the CMake configuration files, or include the + site-packages location in your ``CMAKE_MODULE_PATH``. Or you can use the + new ``pybind11[global]`` extra when you install ``pybind11``, which + installs the CMake files and headers into your base environment in the + standard location. + + * ``pybind11-config`` is another way to write ``python -m pybind11`` if you + have your PATH set up. + + * Added external typing support to the helper module, code from + ``import pybind11`` can now be type checked. + `#2588 `_ + +* Minimum CMake required increased to 3.4. + `#2338 `_ and + `#2370 `_ + + * Full integration with CMake's C++ standard system and compile features + replaces ``PYBIND11_CPP_STANDARD``. + + * Generated config file is now portable to different Python/compiler/CMake + versions. + + * Virtual environments prioritized if ``PYTHON_EXECUTABLE`` is not set + (``venv``, ``virtualenv``, and ``conda``) (similar to the new FindPython + mode). + + * Other CMake features now natively supported, like + ``CMAKE_INTERPROCEDURAL_OPTIMIZATION``, ``set(CMAKE_CXX_VISIBILITY_PRESET + hidden)``. + + * ``CUDA`` as a language is now supported. + + * Helper functions ``pybind11_strip``, ``pybind11_extension``, + ``pybind11_find_import`` added, see :doc:`cmake/index`. + + * Optional :ref:`find-python-mode` and :ref:`nopython-mode` with CMake. + `#2370 `_ + +* Uninstall target added. + `#2265 `_ and + `#2346 `_ + +* ``pybind11_add_module()`` now accepts an optional ``OPT_SIZE`` flag that + switches the binding target to size-based optimization if the global build + type can not always be fixed to ``MinSizeRel`` (except in debug mode, where + optimizations remain disabled). ``MinSizeRel`` or this flag reduces binary + size quite substantially (~25% on some platforms). + `#2463 `_ + +Smaller or developer focused features and fixes: + +* Moved ``mkdoc.py`` to a new repo, `pybind11-mkdoc`_. There are no longer + submodules in the main repo. + +* ``py::memoryview`` segfault fix and update, with new + ``py::memoryview::from_memory`` in Python 3, and documentation. + `#2223 `_ + +* Fix for ``buffer_info`` on Python 2. + `#2503 `_ + +* If ``__eq__`` defined but not ``__hash__``, ``__hash__`` is now set to + ``None``. + `#2291 `_ + +* ``py::ellipsis`` now also works on Python 2. + `#2360 `_ + +* Pointer to ``std::tuple`` & ``std::pair`` supported in cast. + `#2334 `_ + +* Small fixes in NumPy support. ``py::array`` now uses ``py::ssize_t`` as first + argument type. + `#2293 `_ + +* Added missing signature for ``py::array``. + `#2363 `_ + +* ``unchecked_mutable_reference`` has access to operator ``()`` and ``[]`` when + const. + `#2514 `_ + +* ``py::vectorize`` is now supported on functions that return void. + `#1969 `_ + +* ``py::capsule`` supports ``get_pointer`` and ``set_pointer``. + `#1131 `_ + +* Fix crash when different instances share the same pointer of the same type. + `#2252 `_ + +* Fix for ``py::len`` not clearing Python's error state when it fails and throws. + `#2575 `_ + +* Bugfixes related to more extensive testing, new GitHub Actions CI. + `#2321 `_ + +* Bug in timezone issue in Eastern hemisphere midnight fixed. + `#2438 `_ + +* ``std::chrono::time_point`` now works when the resolution is not the same as + the system. + `#2481 `_ + +* Bug fixed where ``py::array_t`` could accept arrays that did not match the + requested ordering. + `#2484 `_ + +* Avoid a segfault on some compilers when types are removed in Python. + `#2564 `_ + +* ``py::arg::none()`` is now also respected when passing keyword arguments. + `#2611 `_ + +* PyPy fixes, PyPy 7.3.x now supported, including PyPy3. (Known issue with + PyPy2 and Windows `#2596 `_). + `#2146 `_ + +* CPython 3.9.0 workaround for undefined behavior (macOS segfault). + `#2576 `_ + +* CPython 3.9 warning fixes. + `#2253 `_ + +* Improved C++20 support, now tested in CI. + `#2489 `_ + `#2599 `_ + +* Improved but still incomplete debug Python interpreter support. + `#2025 `_ + +* NVCC (CUDA 11) now supported and tested in CI. + `#2461 `_ + +* NVIDIA PGI compilers now supported and tested in CI. + `#2475 `_ + +* At least Intel 18 now explicitly required when compiling with Intel. + `#2577 `_ + +* Extensive style checking in CI, with `pre-commit`_ support. Code + modernization, checked by clang-tidy. + +* Expanded docs, including new main page, new installing section, and CMake + helpers page, along with over a dozen new sections on existing pages. + +* In GitHub, new docs for contributing and new issue templates. + +.. _pre-commit: https://pre-commit.com + +.. _pybind11-mkdoc: https://github.com/pybind/pybind11-mkdoc + +v2.5.0 (Mar 31, 2020) +----------------------------------------------------- + +* Use C++17 fold expressions in type casters, if available. This can + improve performance during overload resolution when functions have + multiple arguments. + `#2043 `_. + +* Changed include directory resolution in ``pybind11/__init__.py`` + and installation in ``setup.py``. This fixes a number of open issues + where pybind11 headers could not be found in certain environments. + `#1995 `_. + +* C++20 ``char8_t`` and ``u8string`` support. `#2026 + `_. + +* CMake: search for Python 3.9. `bb9c91 + `_. + +* Fixes for MSYS-based build environments. + `#2087 `_, + `#2053 `_. + +* STL bindings for ``std::vector<...>::clear``. `#2074 + `_. + +* Read-only flag for ``py::buffer``. `#1466 + `_. + +* Exception handling during module initialization. + `bf2b031 `_. + +* Support linking against a CPython debug build. + `#2025 `_. + +* Fixed issues involving the availability and use of aligned ``new`` and + ``delete``. `#1988 `_, + `759221 `_. + +* Fixed a resource leak upon interpreter shutdown. + `#2020 `_. + +* Fixed error handling in the boolean caster. + `#1976 `_. + +v2.4.3 (Oct 15, 2019) +----------------------------------------------------- + +* Adapt pybind11 to a C API convention change in Python 3.8. `#1950 + `_. + +v2.4.2 (Sep 21, 2019) +----------------------------------------------------- + +* Replaced usage of a C++14 only construct. `#1929 + `_. + +* Made an ifdef future-proof for Python >= 4. `f3109d + `_. + +v2.4.1 (Sep 20, 2019) +----------------------------------------------------- + +* Fixed a problem involving implicit conversion from enumerations to integers + on Python 3.8. `#1780 `_. + +v2.4.0 (Sep 19, 2019) +----------------------------------------------------- + +* Try harder to keep pybind11-internal data structures separate when there + are potential ABI incompatibilities. Fixes crashes that occurred when loading + multiple pybind11 extensions that were e.g. compiled by GCC (libstdc++) + and Clang (libc++). + `#1588 `_ and + `c9f5a `_. + +* Added support for ``__await__``, ``__aiter__``, and ``__anext__`` protocols. + `#1842 `_. + +* ``pybind11_add_module()``: don't strip symbols when compiling in + ``RelWithDebInfo`` mode. `#1980 + `_. + +* ``enum_``: Reproduce Python behavior when comparing against invalid values + (e.g. ``None``, strings, etc.). Add back support for ``__invert__()``. + `#1912 `_, + `#1907 `_. + +* List insertion operation for ``py::list``. + Added ``.empty()`` to all collection types. + Added ``py::set::contains()`` and ``py::dict::contains()``. + `#1887 `_, + `#1884 `_, + `#1888 `_. + +* ``py::details::overload_cast_impl`` is available in C++11 mode, can be used + like ``overload_cast`` with an additional set of parentheses. + `#1581 `_. + +* Fixed ``get_include()`` on Conda. + `#1877 `_. + +* ``stl_bind.h``: negative indexing support. + `#1882 `_. + +* Minor CMake fix to add MinGW compatibility. + `#1851 `_. + +* GIL-related fixes. + `#1836 `_, + `8b90b `_. + +* Other very minor/subtle fixes and improvements. + `#1329 `_, + `#1910 `_, + `#1863 `_, + `#1847 `_, + `#1890 `_, + `#1860 `_, + `#1848 `_, + `#1821 `_, + `#1837 `_, + `#1833 `_, + `#1748 `_, + `#1852 `_. + +v2.3.0 (June 11, 2019) +----------------------------------------------------- + +* Significantly reduced module binary size (10-20%) when compiled in C++11 mode + with GCC/Clang, or in any mode with MSVC. Function signatures are now always + precomputed at compile time (this was previously only available in C++14 mode + for non-MSVC compilers). + `#934 `_. + +* Add basic support for tag-based static polymorphism, where classes + provide a method to returns the desired type of an instance. + `#1326 `_. + +* Python type wrappers (``py::handle``, ``py::object``, etc.) + now support map Python's number protocol onto C++ arithmetic + operators such as ``operator+``, ``operator/=``, etc. + `#1511 `_. + +* A number of improvements related to enumerations: + + 1. The ``enum_`` implementation was rewritten from scratch to reduce + code bloat. Rather than instantiating a full implementation for each + enumeration, most code is now contained in a generic base class. + `#1511 `_. + + 2. The ``value()`` method of ``py::enum_`` now accepts an optional + docstring that will be shown in the documentation of the associated + enumeration. `#1160 `_. + + 3. check for already existing enum value and throw an error if present. + `#1453 `_. + +* Support for over-aligned type allocation via C++17's aligned ``new`` + statement. `#1582 `_. + +* Added ``py::ellipsis()`` method for slicing of multidimensional NumPy arrays + `#1502 `_. + +* Numerous Improvements to the ``mkdoc.py`` script for extracting documentation + from C++ header files. + `#1788 `_. + +* ``pybind11_add_module()``: allow including Python as a ``SYSTEM`` include path. + `#1416 `_. + +* ``pybind11/stl.h`` does not convert strings to ``vector`` anymore. + `#1258 `_. + +* Mark static methods as such to fix auto-generated Sphinx documentation. + `#1732 `_. + +* Re-throw forced unwind exceptions (e.g. during pthread termination). + `#1208 `_. + +* Added ``__contains__`` method to the bindings of maps (``std::map``, + ``std::unordered_map``). + `#1767 `_. + +* Improvements to ``gil_scoped_acquire``. + `#1211 `_. + +* Type caster support for ``std::deque``. + `#1609 `_. + +* Support for ``std::unique_ptr`` holders, whose deleters differ between a base and derived + class. `#1353 `_. + +* Construction of STL array/vector-like data structures from + iterators. Added an ``extend()`` operation. + `#1709 `_, + +* CMake build system improvements for projects that include non-C++ + files (e.g. plain C, CUDA) in ``pybind11_add_module`` et al. + `#1678 `_. + +* Fixed asynchronous invocation and deallocation of Python functions + wrapped in ``std::function``. + `#1595 `_. + +* Fixes regarding return value policy propagation in STL type casters. + `#1603 `_. + +* Fixed scoped enum comparisons. + `#1571 `_. + +* Fixed iostream redirection for code that releases the GIL. + `#1368 `_, + +* A number of CI-related fixes. + `#1757 `_, + `#1744 `_, + `#1670 `_. + +v2.2.4 (September 11, 2018) +----------------------------------------------------- + +* Use new Python 3.7 Thread Specific Storage (TSS) implementation if available. + `#1454 `_, + `#1517 `_. + +* Fixes for newer MSVC versions and C++17 mode. + `#1347 `_, + `#1462 `_. + +* Propagate return value policies to type-specific casters + when casting STL containers. + `#1455 `_. + +* Allow ostream-redirection of more than 1024 characters. + `#1479 `_. + +* Set ``Py_DEBUG`` define when compiling against a debug Python build. + `#1438 `_. + +* Untangle integer logic in number type caster to work for custom + types that may only be castable to a restricted set of builtin types. + `#1442 `_. + +* CMake build system: Remember Python version in cache file. + `#1434 `_. + +* Fix for custom smart pointers: use ``std::addressof`` to obtain holder + address instead of ``operator&``. + `#1435 `_. + +* Properly report exceptions thrown during module initialization. + `#1362 `_. + +* Fixed a segmentation fault when creating empty-shaped NumPy array. + `#1371 `_. + +* The version of Intel C++ compiler must be >= 2017, and this is now checked by + the header files. `#1363 `_. + +* A few minor typo fixes and improvements to the test suite, and + patches that silence compiler warnings. + +* Vectors now support construction from generators, as well as ``extend()`` from a + list or generator. + `#1496 `_. + + +v2.2.3 (April 29, 2018) +----------------------------------------------------- + +* The pybind11 header location detection was replaced by a new implementation + that no longer depends on ``pip`` internals (the recently released ``pip`` + 10 has restricted access to this API). + `#1190 `_. + +* Small adjustment to an implementation detail to work around a compiler segmentation fault in Clang 3.3/3.4. + `#1350 `_. + +* The minimal supported version of the Intel compiler was >= 17.0 since + pybind11 v2.1. This check is now explicit, and a compile-time error is raised + if the compiler meet the requirement. + `#1363 `_. + +* Fixed an endianness-related fault in the test suite. + `#1287 `_. + +v2.2.2 (February 7, 2018) +----------------------------------------------------- + +* Fixed a segfault when combining embedded interpreter + shutdown/reinitialization with external loaded pybind11 modules. + `#1092 `_. + +* Eigen support: fixed a bug where Nx1/1xN numpy inputs couldn't be passed as + arguments to Eigen vectors (which for Eigen are simply compile-time fixed + Nx1/1xN matrices). + `#1106 `_. + +* Clarified to license by moving the licensing of contributions from + ``LICENSE`` into ``CONTRIBUTING.md``: the licensing of contributions is not + actually part of the software license as distributed. This isn't meant to be + a substantial change in the licensing of the project, but addresses concerns + that the clause made the license non-standard. + `#1109 `_. + +* Fixed a regression introduced in 2.1 that broke binding functions with lvalue + character literal arguments. + `#1128 `_. + +* MSVC: fix for compilation failures under /permissive-, and added the flag to + the appveyor test suite. + `#1155 `_. + +* Fixed ``__qualname__`` generation, and in turn, fixes how class names + (especially nested class names) are shown in generated docstrings. + `#1171 `_. + +* Updated the FAQ with a suggested project citation reference. + `#1189 `_. + +* Added fixes for deprecation warnings when compiled under C++17 with + ``-Wdeprecated`` turned on, and add ``-Wdeprecated`` to the test suite + compilation flags. + `#1191 `_. + +* Fixed outdated PyPI URLs in ``setup.py``. + `#1213 `_. + +* Fixed a refcount leak for arguments that end up in a ``py::args`` argument + for functions with both fixed positional and ``py::args`` arguments. + `#1216 `_. + +* Fixed a potential segfault resulting from possible premature destruction of + ``py::args``/``py::kwargs`` arguments with overloaded functions. + `#1223 `_. + +* Fixed ``del map[item]`` for a ``stl_bind.h`` bound stl map. + `#1229 `_. + +* Fixed a regression from v2.1.x where the aggregate initialization could + unintentionally end up at a constructor taking a templated + ``std::initializer_list`` argument. + `#1249 `_. + +* Fixed an issue where calling a function with a keep_alive policy on the same + nurse/patient pair would cause the internal patient storage to needlessly + grow (unboundedly, if the nurse is long-lived). + `#1251 `_. + +* Various other minor fixes. + +v2.2.1 (September 14, 2017) +----------------------------------------------------- + +* Added ``py::module_::reload()`` member function for reloading a module. + `#1040 `_. + +* Fixed a reference leak in the number converter. + `#1078 `_. + +* Fixed compilation with Clang on host GCC < 5 (old libstdc++ which isn't fully + C++11 compliant). `#1062 `_. + +* Fixed a regression where the automatic ``std::vector`` caster would + fail to compile. The same fix also applies to any container which returns + element proxies instead of references. + `#1053 `_. + +* Fixed a regression where the ``py::keep_alive`` policy could not be applied + to constructors. `#1065 `_. + +* Fixed a nullptr dereference when loading a ``py::module_local`` type + that's only registered in an external module. + `#1058 `_. + +* Fixed implicit conversion of accessors to types derived from ``py::object``. + `#1076 `_. + +* The ``name`` in ``PYBIND11_MODULE(name, variable)`` can now be a macro. + `#1082 `_. + +* Relaxed overly strict ``py::pickle()`` check for matching get and set types. + `#1064 `_. + +* Conversion errors now try to be more informative when it's likely that + a missing header is the cause (e.g. forgetting ````). + `#1077 `_. + +v2.2.0 (August 31, 2017) +----------------------------------------------------- + +* Support for embedding the Python interpreter. See the + :doc:`documentation page ` for a + full overview of the new features. + `#774 `_, + `#889 `_, + `#892 `_, + `#920 `_. + + .. code-block:: cpp + + #include + namespace py = pybind11; + + int main() { + py::scoped_interpreter guard{}; // start the interpreter and keep it alive + + py::print("Hello, World!"); // use the Python API + } + +* Support for inheriting from multiple C++ bases in Python. + `#693 `_. + + .. code-block:: python + + from cpp_module import CppBase1, CppBase2 + + + class PyDerived(CppBase1, CppBase2): + def __init__(self): + CppBase1.__init__(self) # C++ bases must be initialized explicitly + CppBase2.__init__(self) + +* ``PYBIND11_MODULE`` is now the preferred way to create module entry points. + ``PYBIND11_PLUGIN`` is deprecated. See :ref:`macros` for details. + `#879 `_. + + .. code-block:: cpp + + // new + PYBIND11_MODULE(example, m) { + m.def("add", [](int a, int b) { return a + b; }); + } + + // old + PYBIND11_PLUGIN(example) { + py::module m("example"); + m.def("add", [](int a, int b) { return a + b; }); + return m.ptr(); + } + +* pybind11's headers and build system now more strictly enforce hidden symbol + visibility for extension modules. This should be seamless for most users, + but see the :doc:`upgrade` if you use a custom build system. + `#995 `_. + +* Support for ``py::module_local`` types which allow multiple modules to + export the same C++ types without conflicts. This is useful for opaque + types like ``std::vector``. ``py::bind_vector`` and ``py::bind_map`` + now default to ``py::module_local`` if their elements are builtins or + local types. See :ref:`module_local` for details. + `#949 `_, + `#981 `_, + `#995 `_, + `#997 `_. + +* Custom constructors can now be added very easily using lambdas or factory + functions which return a class instance by value, pointer or holder. This + supersedes the old placement-new ``__init__`` technique. + See :ref:`custom_constructors` for details. + `#805 `_, + `#1014 `_. + + .. code-block:: cpp + + struct Example { + Example(std::string); + }; + + py::class_(m, "Example") + .def(py::init()) // existing constructor + .def(py::init([](int n) { // custom constructor + return std::make_unique(std::to_string(n)); + })); + +* Similarly to custom constructors, pickling support functions are now bound + using the ``py::pickle()`` adaptor which improves type safety. See the + :doc:`upgrade` and :ref:`pickling` for details. + `#1038 `_. + +* Builtin support for converting C++17 standard library types and general + conversion improvements: + + 1. C++17 ``std::variant`` is supported right out of the box. C++11/14 + equivalents (e.g. ``boost::variant``) can also be added with a simple + user-defined specialization. See :ref:`cpp17_container_casters` for details. + `#811 `_, + `#845 `_, + `#989 `_. + + 2. Out-of-the-box support for C++17 ``std::string_view``. + `#906 `_. + + 3. Improved compatibility of the builtin ``optional`` converter. + `#874 `_. + + 4. The ``bool`` converter now accepts ``numpy.bool_`` and types which + define ``__bool__`` (Python 3.x) or ``__nonzero__`` (Python 2.7). + `#925 `_. + + 5. C++-to-Python casters are now more efficient and move elements out + of rvalue containers whenever possible. + `#851 `_, + `#936 `_, + `#938 `_. + + 6. Fixed ``bytes`` to ``std::string/char*`` conversion on Python 3. + `#817 `_. + + 7. Fixed lifetime of temporary C++ objects created in Python-to-C++ conversions. + `#924 `_. + +* Scope guard call policy for RAII types, e.g. ``py::call_guard()``, + ``py::call_guard()``. See :ref:`call_policies` for details. + `#740 `_. + +* Utility for redirecting C++ streams to Python (e.g. ``std::cout`` -> + ``sys.stdout``). Scope guard ``py::scoped_ostream_redirect`` in C++ and + a context manager in Python. See :ref:`ostream_redirect`. + `#1009 `_. + +* Improved handling of types and exceptions across module boundaries. + `#915 `_, + `#951 `_, + `#995 `_. + +* Fixed destruction order of ``py::keep_alive`` nurse/patient objects + in reference cycles. + `#856 `_. + +* NumPy and buffer protocol related improvements: + + 1. Support for negative strides in Python buffer objects/numpy arrays. This + required changing integers from unsigned to signed for the related C++ APIs. + Note: If you have compiler warnings enabled, you may notice some new conversion + warnings after upgrading. These can be resolved with ``static_cast``. + `#782 `_. + + 2. Support ``std::complex`` and arrays inside ``PYBIND11_NUMPY_DTYPE``. + `#831 `_, + `#832 `_. + + 3. Support for constructing ``py::buffer_info`` and ``py::arrays`` using + arbitrary containers or iterators instead of requiring a ``std::vector``. + `#788 `_, + `#822 `_, + `#860 `_. + + 4. Explicitly check numpy version and require >= 1.7.0. + `#819 `_. + +* Support for allowing/prohibiting ``None`` for specific arguments and improved + ``None`` overload resolution order. See :ref:`none_arguments` for details. + `#843 `_. + `#859 `_. + +* Added ``py::exec()`` as a shortcut for ``py::eval()`` + and support for C++11 raw string literals as input. See :ref:`eval`. + `#766 `_, + `#827 `_. + +* ``py::vectorize()`` ignores non-vectorizable arguments and supports + member functions. + `#762 `_. + +* Support for bound methods as callbacks (``pybind11/functional.h``). + `#815 `_. + +* Allow aliasing pybind11 methods: ``cls.attr("foo") = cls.attr("bar")``. + `#802 `_. + +* Don't allow mixed static/non-static overloads. + `#804 `_. + +* Fixed overriding static properties in derived classes. + `#784 `_. + +* Added support for write only properties. + `#1144 `_. + +* Improved deduction of member functions of a derived class when its bases + aren't registered with pybind11. + `#855 `_. + + .. code-block:: cpp + + struct Base { + int foo() { return 42; } + } + + struct Derived : Base {} + + // Now works, but previously required also binding `Base` + py::class_(m, "Derived") + .def("foo", &Derived::foo); // function is actually from `Base` + +* The implementation of ``py::init<>`` now uses C++11 brace initialization + syntax to construct instances, which permits binding implicit constructors of + aggregate types. `#1015 `_. + + .. code-block:: cpp + + struct Aggregate { + int a; + std::string b; + }; + + py::class_(m, "Aggregate") + .def(py::init()); + +* Fixed issues with multiple inheritance with offset base/derived pointers. + `#812 `_, + `#866 `_, + `#960 `_. + +* Fixed reference leak of type objects. + `#1030 `_. + +* Improved support for the ``/std:c++14`` and ``/std:c++latest`` modes + on MSVC 2017. + `#841 `_, + `#999 `_. + +* Fixed detection of private operator new on MSVC. + `#893 `_, + `#918 `_. + +* Intel C++ compiler compatibility fixes. + `#937 `_. + +* Fixed implicit conversion of ``py::enum_`` to integer types on Python 2.7. + `#821 `_. + +* Added ``py::hash`` to fetch the hash value of Python objects, and + ``.def(hash(py::self))`` to provide the C++ ``std::hash`` as the Python + ``__hash__`` method. + `#1034 `_. + +* Fixed ``__truediv__`` on Python 2 and ``__itruediv__`` on Python 3. + `#867 `_. + +* ``py::capsule`` objects now support the ``name`` attribute. This is useful + for interfacing with ``scipy.LowLevelCallable``. + `#902 `_. + +* Fixed ``py::make_iterator``'s ``__next__()`` for past-the-end calls. + `#897 `_. + +* Added ``error_already_set::matches()`` for checking Python exceptions. + `#772 `_. + +* Deprecated ``py::error_already_set::clear()``. It's no longer needed + following a simplification of the ``py::error_already_set`` class. + `#954 `_. + +* Deprecated ``py::handle::operator==()`` in favor of ``py::handle::is()`` + `#825 `_. + +* Deprecated ``py::object::borrowed``/``py::object::stolen``. + Use ``py::object::borrowed_t{}``/``py::object::stolen_t{}`` instead. + `#771 `_. + +* Changed internal data structure versioning to avoid conflicts between + modules compiled with different revisions of pybind11. + `#1012 `_. + +* Additional compile-time and run-time error checking and more informative messages. + `#786 `_, + `#794 `_, + `#803 `_. + +* Various minor improvements and fixes. + `#764 `_, + `#791 `_, + `#795 `_, + `#840 `_, + `#844 `_, + `#846 `_, + `#849 `_, + `#858 `_, + `#862 `_, + `#871 `_, + `#872 `_, + `#881 `_, + `#888 `_, + `#899 `_, + `#928 `_, + `#931 `_, + `#944 `_, + `#950 `_, + `#952 `_, + `#962 `_, + `#965 `_, + `#970 `_, + `#978 `_, + `#979 `_, + `#986 `_, + `#1020 `_, + `#1027 `_, + `#1037 `_. + +* Testing improvements. + `#798 `_, + `#882 `_, + `#898 `_, + `#900 `_, + `#921 `_, + `#923 `_, + `#963 `_. + +v2.1.1 (April 7, 2017) +----------------------------------------------------- + +* Fixed minimum version requirement for MSVC 2015u3 + `#773 `_. + +v2.1.0 (March 22, 2017) +----------------------------------------------------- + +* pybind11 now performs function overload resolution in two phases. The first + phase only considers exact type matches, while the second allows for implicit + conversions to take place. A special ``noconvert()`` syntax can be used to + completely disable implicit conversions for specific arguments. + `#643 `_, + `#634 `_, + `#650 `_. + +* Fixed a regression where static properties no longer worked with classes + using multiple inheritance. The ``py::metaclass`` attribute is no longer + necessary (and deprecated as of this release) when binding classes with + static properties. + `#679 `_, + +* Classes bound using ``pybind11`` can now use custom metaclasses. + `#679 `_, + +* ``py::args`` and ``py::kwargs`` can now be mixed with other positional + arguments when binding functions using pybind11. + `#611 `_. + +* Improved support for C++11 unicode string and character types; added + extensive documentation regarding pybind11's string conversion behavior. + `#624 `_, + `#636 `_, + `#715 `_. + +* pybind11 can now avoid expensive copies when converting Eigen arrays to NumPy + arrays (and vice versa). `#610 `_. + +* The "fast path" in ``py::vectorize`` now works for any full-size group of C or + F-contiguous arrays. The non-fast path is also faster since it no longer performs + copies of the input arguments (except when type conversions are necessary). + `#610 `_. + +* Added fast, unchecked access to NumPy arrays via a proxy object. + `#746 `_. + +* Transparent support for class-specific ``operator new`` and + ``operator delete`` implementations. + `#755 `_. + +* Slimmer and more efficient STL-compatible iterator interface for sequence types. + `#662 `_. + +* Improved custom holder type support. + `#607 `_. + +* ``nullptr`` to ``None`` conversion fixed in various builtin type casters. + `#732 `_. + +* ``enum_`` now exposes its members via a special ``__members__`` attribute. + `#666 `_. + +* ``std::vector`` bindings created using ``stl_bind.h`` can now optionally + implement the buffer protocol. `#488 `_. + +* Automated C++ reference documentation using doxygen and breathe. + `#598 `_. + +* Added minimum compiler version assertions. + `#727 `_. + +* Improved compatibility with C++1z. + `#677 `_. + +* Improved ``py::capsule`` API. Can be used to implement cleanup + callbacks that are involved at module destruction time. + `#752 `_. + +* Various minor improvements and fixes. + `#595 `_, + `#588 `_, + `#589 `_, + `#603 `_, + `#619 `_, + `#648 `_, + `#695 `_, + `#720 `_, + `#723 `_, + `#729 `_, + `#724 `_, + `#742 `_, + `#753 `_. + +v2.0.1 (Jan 4, 2017) +----------------------------------------------------- + +* Fix pointer to reference error in type_caster on MSVC + `#583 `_. + +* Fixed a segmentation in the test suite due to a typo + `cd7eac `_. + +v2.0.0 (Jan 1, 2017) +----------------------------------------------------- + +* Fixed a reference counting regression affecting types with custom metaclasses + (introduced in v2.0.0-rc1). + `#571 `_. + +* Quenched a CMake policy warning. + `#570 `_. + +v2.0.0-rc1 (Dec 23, 2016) +----------------------------------------------------- + +The pybind11 developers are excited to issue a release candidate of pybind11 +with a subsequent v2.0.0 release planned in early January next year. + +An incredible amount of effort by went into pybind11 over the last ~5 months, +leading to a release that is jam-packed with exciting new features and numerous +usability improvements. The following list links PRs or individual commits +whenever applicable. + +Happy Christmas! + +* Support for binding C++ class hierarchies that make use of multiple + inheritance. `#410 `_. + +* PyPy support: pybind11 now supports nightly builds of PyPy and will + interoperate with the future 5.7 release. No code changes are necessary, + everything "just" works as usual. Note that we only target the Python 2.7 + branch for now; support for 3.x will be added once its ``cpyext`` extension + support catches up. A few minor features remain unsupported for the time + being (notably dynamic attributes in custom types). + `#527 `_. + +* Significant work on the documentation -- in particular, the monolithic + ``advanced.rst`` file was restructured into a easier to read hierarchical + organization. `#448 `_. + +* Many NumPy-related improvements: + + 1. Object-oriented API to access and modify NumPy ``ndarray`` instances, + replicating much of the corresponding NumPy C API functionality. + `#402 `_. + + 2. NumPy array ``dtype`` array descriptors are now first-class citizens and + are exposed via a new class ``py::dtype``. + + 3. Structured dtypes can be registered using the ``PYBIND11_NUMPY_DTYPE()`` + macro. Special ``array`` constructors accepting dtype objects were also + added. + + One potential caveat involving this change: format descriptor strings + should now be accessed via ``format_descriptor::format()`` (however, for + compatibility purposes, the old syntax ``format_descriptor::value`` will + still work for non-structured data types). `#308 + `_. + + 4. Further improvements to support structured dtypes throughout the system. + `#472 `_, + `#474 `_, + `#459 `_, + `#453 `_, + `#452 `_, and + `#505 `_. + + 5. Fast access operators. `#497 `_. + + 6. Constructors for arrays whose storage is owned by another object. + `#440 `_. + + 7. Added constructors for ``array`` and ``array_t`` explicitly accepting shape + and strides; if strides are not provided, they are deduced assuming + C-contiguity. Also added simplified constructors for 1-dimensional case. + + 8. Added buffer/NumPy support for ``char[N]`` and ``std::array`` types. + + 9. Added ``memoryview`` wrapper type which is constructible from ``buffer_info``. + +* Eigen: many additional conversions and support for non-contiguous + arrays/slices. + `#427 `_, + `#315 `_, + `#316 `_, + `#312 `_, and + `#267 `_ + +* Incompatible changes in ``class_<...>::class_()``: + + 1. Declarations of types that provide access via the buffer protocol must + now include the ``py::buffer_protocol()`` annotation as an argument to + the ``class_`` constructor. + + 2. Declarations of types that require a custom metaclass (i.e. all classes + which include static properties via commands such as + ``def_readwrite_static()``) must now include the ``py::metaclass()`` + annotation as an argument to the ``class_`` constructor. + + These two changes were necessary to make type definitions in pybind11 + future-proof, and to support PyPy via its cpyext mechanism. `#527 + `_. + + + 3. This version of pybind11 uses a redesigned mechanism for instantiating + trampoline classes that are used to override virtual methods from within + Python. This led to the following user-visible syntax change: instead of + + .. code-block:: cpp + + py::class_("MyClass") + .alias() + .... + + write + + .. code-block:: cpp + + py::class_("MyClass") + .... + + Importantly, both the original and the trampoline class are now + specified as an arguments (in arbitrary order) to the ``py::class_`` + template, and the ``alias<..>()`` call is gone. The new scheme has zero + overhead in cases when Python doesn't override any functions of the + underlying C++ class. `rev. 86d825 + `_. + +* Added ``eval`` and ``eval_file`` functions for evaluating expressions and + statements from a string or file. `rev. 0d3fc3 + `_. + +* pybind11 can now create types with a modifiable dictionary. + `#437 `_ and + `#444 `_. + +* Support for translation of arbitrary C++ exceptions to Python counterparts. + `#296 `_ and + `#273 `_. + +* Report full backtraces through mixed C++/Python code, better reporting for + import errors, fixed GIL management in exception processing. + `#537 `_, + `#494 `_, + `rev. e72d95 `_, and + `rev. 099d6e `_. + +* Support for bit-level operations, comparisons, and serialization of C++ + enumerations. `#503 `_, + `#508 `_, + `#380 `_, + `#309 `_. + `#311 `_. + +* The ``class_`` constructor now accepts its template arguments in any order. + `#385 `_. + +* Attribute and item accessors now have a more complete interface which makes + it possible to chain attributes as in + ``obj.attr("a")[key].attr("b").attr("method")(1, 2, 3)``. `#425 + `_. + +* Major redesign of the default and conversion constructors in ``pytypes.h``. + `#464 `_. + +* Added built-in support for ``std::shared_ptr`` holder type. It is no longer + necessary to to include a declaration of the form + ``PYBIND11_DECLARE_HOLDER_TYPE(T, std::shared_ptr)`` (though continuing to + do so won't cause an error). + `#454 `_. + +* New ``py::overload_cast`` casting operator to select among multiple possible + overloads of a function. An example: + + .. code-block:: cpp + + py::class_(m, "Pet") + .def("set", py::overload_cast(&Pet::set), "Set the pet's age") + .def("set", py::overload_cast(&Pet::set), "Set the pet's name"); + + This feature only works on C++14-capable compilers. + `#541 `_. + +* C++ types are automatically cast to Python types, e.g. when assigning + them as an attribute. For instance, the following is now legal: + + .. code-block:: cpp + + py::module m = /* ... */ + m.attr("constant") = 123; + + (Previously, a ``py::cast`` call was necessary to avoid a compilation error.) + `#551 `_. + +* Redesigned ``pytest``-based test suite. `#321 `_. + +* Instance tracking to detect reference leaks in test suite. `#324 `_ + +* pybind11 can now distinguish between multiple different instances that are + located at the same memory address, but which have different types. + `#329 `_. + +* Improved logic in ``move`` return value policy. + `#510 `_, + `#297 `_. + +* Generalized unpacking API to permit calling Python functions from C++ using + notation such as ``foo(a1, a2, *args, "ka"_a=1, "kb"_a=2, **kwargs)``. `#372 `_. + +* ``py::print()`` function whose behavior matches that of the native Python + ``print()`` function. `#372 `_. + +* Added ``py::dict`` keyword constructor:``auto d = dict("number"_a=42, + "name"_a="World");``. `#372 `_. + +* Added ``py::str::format()`` method and ``_s`` literal: ``py::str s = "1 + 2 + = {}"_s.format(3);``. `#372 `_. + +* Added ``py::repr()`` function which is equivalent to Python's builtin + ``repr()``. `#333 `_. + +* Improved construction and destruction logic for holder types. It is now + possible to reference instances with smart pointer holder types without + constructing the holder if desired. The ``PYBIND11_DECLARE_HOLDER_TYPE`` + macro now accepts an optional second parameter to indicate whether the holder + type uses intrusive reference counting. + `#533 `_ and + `#561 `_. + +* Mapping a stateless C++ function to Python and back is now "for free" (i.e. + no extra indirections or argument conversion overheads). `rev. 954b79 + `_. + +* Bindings for ``std::valarray``. + `#545 `_. + +* Improved support for C++17 capable compilers. + `#562 `_. + +* Bindings for ``std::optional``. + `#475 `_, + `#476 `_, + `#479 `_, + `#499 `_, and + `#501 `_. + +* ``stl_bind.h``: general improvements and support for ``std::map`` and + ``std::unordered_map``. + `#490 `_, + `#282 `_, + `#235 `_. + +* The ``std::tuple``, ``std::pair``, ``std::list``, and ``std::vector`` type + casters now accept any Python sequence type as input. `rev. 107285 + `_. + +* Improved CMake Python detection on multi-architecture Linux. + `#532 `_. + +* Infrastructure to selectively disable or enable parts of the automatically + generated docstrings. `#486 `_. + +* ``reference`` and ``reference_internal`` are now the default return value + properties for static and non-static properties, respectively. `#473 + `_. (the previous defaults + were ``automatic``). `#473 `_. + +* Support for ``std::unique_ptr`` with non-default deleters or no deleter at + all (``py::nodelete``). `#384 `_. + +* Deprecated ``handle::call()`` method. The new syntax to call Python + functions is simply ``handle()``. It can also be invoked explicitly via + ``handle::operator()``, where ``X`` is an optional return value policy. + +* Print more informative error messages when ``make_tuple()`` or ``cast()`` + fail. `#262 `_. + +* Creation of holder types for classes deriving from + ``std::enable_shared_from_this<>`` now also works for ``const`` values. + `#260 `_. + +* ``make_iterator()`` improvements for better compatibility with various + types (now uses prefix increment operator); it now also accepts iterators + with different begin/end types as long as they are equality comparable. + `#247 `_. + +* ``arg()`` now accepts a wider range of argument types for default values. + `#244 `_. + +* Support ``keep_alive`` where the nurse object may be ``None``. `#341 + `_. + +* Added constructors for ``str`` and ``bytes`` from zero-terminated char + pointers, and from char pointers and length. Added constructors for ``str`` + from ``bytes`` and for ``bytes`` from ``str``, which will perform UTF-8 + decoding/encoding as required. + +* Many other improvements of library internals without user-visible changes + + +1.8.1 (July 12, 2016) +---------------------- +* Fixed a rare but potentially very severe issue when the garbage collector ran + during pybind11 type creation. + +1.8.0 (June 14, 2016) +---------------------- +* Redesigned CMake build system which exports a convenient + ``pybind11_add_module`` function to parent projects. +* ``std::vector<>`` type bindings analogous to Boost.Python's ``indexing_suite`` +* Transparent conversion of sparse and dense Eigen matrices and vectors (``eigen.h``) +* Added an ``ExtraFlags`` template argument to the NumPy ``array_t<>`` wrapper + to disable an enforced cast that may lose precision, e.g. to create overloads + for different precisions and complex vs real-valued matrices. +* Prevent implicit conversion of floating point values to integral types in + function arguments +* Fixed incorrect default return value policy for functions returning a shared + pointer +* Don't allow registering a type via ``class_`` twice +* Don't allow casting a ``None`` value into a C++ lvalue reference +* Fixed a crash in ``enum_::operator==`` that was triggered by the ``help()`` command +* Improved detection of whether or not custom C++ types can be copy/move-constructed +* Extended ``str`` type to also work with ``bytes`` instances +* Added a ``"name"_a`` user defined string literal that is equivalent to ``py::arg("name")``. +* When specifying function arguments via ``py::arg``, the test that verifies + the number of arguments now runs at compile time. +* Added ``[[noreturn]]`` attribute to ``pybind11_fail()`` to quench some + compiler warnings +* List function arguments in exception text when the dispatch code cannot find + a matching overload +* Added ``PYBIND11_OVERLOAD_NAME`` and ``PYBIND11_OVERLOAD_PURE_NAME`` macros which + can be used to override virtual methods whose name differs in C++ and Python + (e.g. ``__call__`` and ``operator()``) +* Various minor ``iterator`` and ``make_iterator()`` improvements +* Transparently support ``__bool__`` on Python 2.x and Python 3.x +* Fixed issue with destructor of unpickled object not being called +* Minor CMake build system improvements on Windows +* New ``pybind11::args`` and ``pybind11::kwargs`` types to create functions which + take an arbitrary number of arguments and keyword arguments +* New syntax to call a Python function from C++ using ``*args`` and ``*kwargs`` +* The functions ``def_property_*`` now correctly process docstring arguments (these + formerly caused a segmentation fault) +* Many ``mkdoc.py`` improvements (enumerations, template arguments, ``DOC()`` + macro accepts more arguments) +* Cygwin support +* Documentation improvements (pickling support, ``keep_alive``, macro usage) + +1.7 (April 30, 2016) +---------------------- +* Added a new ``move`` return value policy that triggers C++11 move semantics. + The automatic return value policy falls back to this case whenever a rvalue + reference is encountered +* Significantly more general GIL state routines that are used instead of + Python's troublesome ``PyGILState_Ensure`` and ``PyGILState_Release`` API +* Redesign of opaque types that drastically simplifies their usage +* Extended ability to pass values of type ``[const] void *`` +* ``keep_alive`` fix: don't fail when there is no patient +* ``functional.h``: acquire the GIL before calling a Python function +* Added Python RAII type wrappers ``none`` and ``iterable`` +* Added ``*args`` and ``*kwargs`` pass-through parameters to + ``pybind11.get_include()`` function +* Iterator improvements and fixes +* Documentation on return value policies and opaque types improved + +1.6 (April 30, 2016) +---------------------- +* Skipped due to upload to PyPI gone wrong and inability to recover + (https://github.com/pypa/packaging-problems/issues/74) + +1.5 (April 21, 2016) +---------------------- +* For polymorphic types, use RTTI to try to return the closest type registered with pybind11 +* Pickling support for serializing and unserializing C++ instances to a byte stream in Python +* Added a convenience routine ``make_iterator()`` which turns a range indicated + by a pair of C++ iterators into a iterable Python object +* Added ``len()`` and a variadic ``make_tuple()`` function +* Addressed a rare issue that could confuse the current virtual function + dispatcher and another that could lead to crashes in multi-threaded + applications +* Added a ``get_include()`` function to the Python module that returns the path + of the directory containing the installed pybind11 header files +* Documentation improvements: import issues, symbol visibility, pickling, limitations +* Added casting support for ``std::reference_wrapper<>`` + +1.4 (April 7, 2016) +-------------------------- +* Transparent type conversion for ``std::wstring`` and ``wchar_t`` +* Allow passing ``nullptr``-valued strings +* Transparent passing of ``void *`` pointers using capsules +* Transparent support for returning values wrapped in ``std::unique_ptr<>`` +* Improved docstring generation for compatibility with Sphinx +* Nicer debug error message when default parameter construction fails +* Support for "opaque" types that bypass the transparent conversion layer for STL containers +* Redesigned type casting interface to avoid ambiguities that could occasionally cause compiler errors +* Redesigned property implementation; fixes crashes due to an unfortunate default return value policy +* Anaconda package generation support + +1.3 (March 8, 2016) +-------------------------- + +* Added support for the Intel C++ compiler (v15+) +* Added support for the STL unordered set/map data structures +* Added support for the STL linked list data structure +* NumPy-style broadcasting support in ``pybind11::vectorize`` +* pybind11 now displays more verbose error messages when ``arg::operator=()`` fails +* pybind11 internal data structures now live in a version-dependent namespace to avoid ABI issues +* Many, many bugfixes involving corner cases and advanced usage + +1.2 (February 7, 2016) +-------------------------- + +* Optional: efficient generation of function signatures at compile time using C++14 +* Switched to a simpler and more general way of dealing with function default + arguments. Unused keyword arguments in function calls are now detected and + cause errors as expected +* New ``keep_alive`` call policy analogous to Boost.Python's ``with_custodian_and_ward`` +* New ``pybind11::base<>`` attribute to indicate a subclass relationship +* Improved interface for RAII type wrappers in ``pytypes.h`` +* Use RAII type wrappers consistently within pybind11 itself. This + fixes various potential refcount leaks when exceptions occur +* Added new ``bytes`` RAII type wrapper (maps to ``string`` in Python 2.7) +* Made handle and related RAII classes const correct, using them more + consistently everywhere now +* Got rid of the ugly ``__pybind11__`` attributes on the Python side---they are + now stored in a C++ hash table that is not visible in Python +* Fixed refcount leaks involving NumPy arrays and bound functions +* Vastly improved handling of shared/smart pointers +* Removed an unnecessary copy operation in ``pybind11::vectorize`` +* Fixed naming clashes when both pybind11 and NumPy headers are included +* Added conversions for additional exception types +* Documentation improvements (using multiple extension modules, smart pointers, + other minor clarifications) +* unified infrastructure for parsing variadic arguments in ``class_`` and cpp_function +* Fixed license text (was: ZLIB, should have been: 3-clause BSD) +* Python 3.2 compatibility +* Fixed remaining issues when accessing types in another plugin module +* Added enum comparison and casting methods +* Improved SFINAE-based detection of whether types are copy-constructible +* Eliminated many warnings about unused variables and the use of ``offsetof()`` +* Support for ``std::array<>`` conversions + +1.1 (December 7, 2015) +-------------------------- + +* Documentation improvements (GIL, wrapping functions, casting, fixed many typos) +* Generalized conversion of integer types +* Improved support for casting function objects +* Improved support for ``std::shared_ptr<>`` conversions +* Initial support for ``std::set<>`` conversions +* Fixed type resolution issue for types defined in a separate plugin module +* CMake build system improvements +* Factored out generic functionality to non-templated code (smaller code size) +* Added a code size / compile time benchmark vs Boost.Python +* Added an appveyor CI script + +1.0 (October 15, 2015) +------------------------ +* Initial release diff --git a/extern/pybind11/docs/classes.rst b/extern/pybind11/docs/classes.rst new file mode 100644 index 000000000..4f2167dac --- /dev/null +++ b/extern/pybind11/docs/classes.rst @@ -0,0 +1,555 @@ +.. _classes: + +Object-oriented code +#################### + +Creating bindings for a custom type +=================================== + +Let's now look at a more complex example where we'll create bindings for a +custom C++ data structure named ``Pet``. Its definition is given below: + +.. code-block:: cpp + + struct Pet { + Pet(const std::string &name) : name(name) { } + void setName(const std::string &name_) { name = name_; } + const std::string &getName() const { return name; } + + std::string name; + }; + +The binding code for ``Pet`` looks as follows: + +.. code-block:: cpp + + #include + + namespace py = pybind11; + + PYBIND11_MODULE(example, m) { + py::class_(m, "Pet") + .def(py::init()) + .def("setName", &Pet::setName) + .def("getName", &Pet::getName); + } + +:class:`class_` creates bindings for a C++ *class* or *struct*-style data +structure. :func:`init` is a convenience function that takes the types of a +constructor's parameters as template arguments and wraps the corresponding +constructor (see the :ref:`custom_constructors` section for details). An +interactive Python session demonstrating this example is shown below: + +.. code-block:: pycon + + % python + >>> import example + >>> p = example.Pet("Molly") + >>> print(p) + + >>> p.getName() + 'Molly' + >>> p.setName("Charly") + >>> p.getName() + 'Charly' + +.. seealso:: + + 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 +discussed in the previous chapter. Refer to the sections :ref:`keyword_args` +and :ref:`default_args` for details. + +Binding lambda functions +======================== + +Note how ``print(p)`` produced a rather useless summary of our data structure in the example above: + +.. code-block:: pycon + + >>> print(p) + + +To address this, we could bind a utility function that returns a human-readable +summary to the special method slot named ``__repr__``. Unfortunately, there is no +suitable functionality in the ``Pet`` data structure, and it would be nice if +we did not have to change it. This can easily be accomplished by binding a +Lambda function instead: + +.. code-block:: cpp + + py::class_(m, "Pet") + .def(py::init()) + .def("setName", &Pet::setName) + .def("getName", &Pet::getName) + .def("__repr__", + [](const Pet &a) { + return ""; + } + ); + +Both stateless [#f1]_ and stateful lambda closures are supported by pybind11. +With the above change, the same Python code now produces the following output: + +.. code-block:: pycon + + >>> print(p) + + +.. [#f1] Stateless closures are those with an empty pair of brackets ``[]`` as the capture object. + +.. _properties: + +Instance and static fields +========================== + +We can also directly expose the ``name`` field using the +:func:`class_::def_readwrite` method. A similar :func:`class_::def_readonly` +method also exists for ``const`` fields. + +.. code-block:: cpp + + py::class_(m, "Pet") + .def(py::init()) + .def_readwrite("name", &Pet::name) + // ... remainder ... + +This makes it possible to write + +.. code-block:: pycon + + >>> p = example.Pet("Molly") + >>> p.name + 'Molly' + >>> p.name = "Charly" + >>> p.name + 'Charly' + +Now suppose that ``Pet::name`` was a private internal variable +that can only be accessed via setters and getters. + +.. code-block:: cpp + + class Pet { + public: + Pet(const std::string &name) : name(name) { } + void setName(const std::string &name_) { name = name_; } + const std::string &getName() const { return name; } + private: + std::string name; + }; + +In this case, the method :func:`class_::def_property` +(:func:`class_::def_property_readonly` for read-only data) can be used to +provide a field-like interface within Python that will transparently call +the setter and getter functions: + +.. code-block:: cpp + + py::class_(m, "Pet") + .def(py::init()) + .def_property("name", &Pet::getName, &Pet::setName) + // ... remainder ... + +Write only properties can be defined by passing ``nullptr`` as the +input for the read function. + +.. seealso:: + + Similar functions :func:`class_::def_readwrite_static`, + :func:`class_::def_readonly_static` :func:`class_::def_property_static`, + and :func:`class_::def_property_readonly_static` are provided for binding + static variables and properties. Please also see the section on + :ref:`static_properties` in the advanced part of the documentation. + +Dynamic attributes +================== + +Native Python classes can pick up new attributes dynamically: + +.. code-block:: pycon + + >>> class Pet: + ... name = "Molly" + ... + >>> p = Pet() + >>> p.name = "Charly" # overwrite existing + >>> p.age = 2 # dynamically add a new attribute + +By default, classes exported from C++ do not support this and the only writable +attributes are the ones explicitly defined using :func:`class_::def_readwrite` +or :func:`class_::def_property`. + +.. code-block:: cpp + + py::class_(m, "Pet") + .def(py::init<>()) + .def_readwrite("name", &Pet::name); + +Trying to set any other attribute results in an error: + +.. code-block:: pycon + + >>> p = example.Pet() + >>> p.name = "Charly" # OK, attribute defined in C++ + >>> p.age = 2 # fail + AttributeError: 'Pet' object has no attribute 'age' + +To enable dynamic attributes for C++ classes, the :class:`py::dynamic_attr` tag +must be added to the :class:`py::class_` constructor: + +.. code-block:: cpp + + py::class_(m, "Pet", py::dynamic_attr()) + .def(py::init<>()) + .def_readwrite("name", &Pet::name); + +Now everything works as expected: + +.. code-block:: pycon + + >>> p = example.Pet() + >>> p.name = "Charly" # OK, overwrite value in C++ + >>> p.age = 2 # OK, dynamically add a new attribute + >>> p.__dict__ # just like a native Python class + {'age': 2} + +Note that there is a small runtime cost for a class with dynamic attributes. +Not only because of the addition of a ``__dict__``, but also because of more +expensive garbage collection tracking which must be activated to resolve +possible circular references. Native Python classes incur this same cost by +default, so this is not anything to worry about. By default, pybind11 classes +are more efficient than native Python classes. Enabling dynamic attributes +just brings them on par. + +.. _inheritance: + +Inheritance and automatic downcasting +===================================== + +Suppose now that the example consists of two data structures with an +inheritance relationship: + +.. code-block:: cpp + + struct Pet { + Pet(const std::string &name) : name(name) { } + std::string name; + }; + + struct Dog : Pet { + Dog(const std::string &name) : Pet(name) { } + std::string bark() const { return "woof!"; } + }; + +There are two different ways of indicating a hierarchical relationship to +pybind11: the first specifies the C++ base class as an extra template +parameter of the :class:`class_`: + +.. code-block:: cpp + + py::class_(m, "Pet") + .def(py::init()) + .def_readwrite("name", &Pet::name); + + // Method 1: template parameter: + py::class_(m, "Dog") + .def(py::init()) + .def("bark", &Dog::bark); + +Alternatively, we can also assign a name to the previously bound ``Pet`` +:class:`class_` object and reference it when binding the ``Dog`` class: + +.. code-block:: cpp + + py::class_ pet(m, "Pet"); + pet.def(py::init()) + .def_readwrite("name", &Pet::name); + + // Method 2: pass parent class_ object: + py::class_(m, "Dog", pet /* <- specify Python parent type */) + .def(py::init()) + .def("bark", &Dog::bark); + +Functionality-wise, both approaches are equivalent. Afterwards, instances will +expose fields and methods of both types: + +.. code-block:: pycon + + >>> p = example.Dog("Molly") + >>> p.name + 'Molly' + >>> p.bark() + 'woof!' + +The C++ classes defined above are regular non-polymorphic types with an +inheritance relationship. This is reflected in Python: + +.. code-block:: cpp + + // Return a base pointer to a derived instance + m.def("pet_store", []() { return std::unique_ptr(new Dog("Molly")); }); + +.. code-block:: pycon + + >>> p = example.pet_store() + >>> type(p) # `Dog` instance behind `Pet` pointer + Pet # no pointer downcasting for regular non-polymorphic types + >>> p.bark() + AttributeError: 'Pet' object has no attribute 'bark' + +The function returned a ``Dog`` instance, but because it's a non-polymorphic +type behind a base pointer, Python only sees a ``Pet``. In C++, a type is only +considered polymorphic if it has at least one virtual function and pybind11 +will automatically recognize this: + +.. code-block:: cpp + + struct PolymorphicPet { + virtual ~PolymorphicPet() = default; + }; + + struct PolymorphicDog : PolymorphicPet { + std::string bark() const { return "woof!"; } + }; + + // Same binding code + py::class_(m, "PolymorphicPet"); + py::class_(m, "PolymorphicDog") + .def(py::init<>()) + .def("bark", &PolymorphicDog::bark); + + // Again, return a base pointer to a derived instance + m.def("pet_store2", []() { return std::unique_ptr(new PolymorphicDog); }); + +.. code-block:: pycon + + >>> p = example.pet_store2() + >>> type(p) + PolymorphicDog # automatically downcast + >>> p.bark() + 'woof!' + +Given a pointer to a polymorphic base, pybind11 performs automatic downcasting +to the actual derived type. Note that this goes beyond the usual situation in +C++: we don't just get access to the virtual functions of the base, we get the +concrete derived type including functions and attributes that the base type may +not even be aware of. + +.. seealso:: + + For more information about polymorphic behavior see :ref:`overriding_virtuals`. + + +Overloaded methods +================== + +Sometimes there are several overloaded C++ methods with the same name taking +different kinds of input arguments: + +.. code-block:: cpp + + struct Pet { + Pet(const std::string &name, int age) : name(name), age(age) { } + + void set(int age_) { age = age_; } + void set(const std::string &name_) { name = name_; } + + std::string name; + int age; + }; + +Attempting to bind ``Pet::set`` will cause an error since the compiler does not +know which method the user intended to select. We can disambiguate by casting +them to function pointers. Binding multiple functions to the same Python name +automatically creates a chain of function overloads that will be tried in +sequence. + +.. code-block:: cpp + + py::class_(m, "Pet") + .def(py::init()) + .def("set", static_cast(&Pet::set), "Set the pet's age") + .def("set", static_cast(&Pet::set), "Set the pet's name"); + +The overload signatures are also visible in the method's docstring: + +.. code-block:: pycon + + >>> help(example.Pet) + + class Pet(__builtin__.object) + | Methods defined here: + | + | __init__(...) + | Signature : (Pet, str, int) -> NoneType + | + | set(...) + | 1. Signature : (Pet, int) -> NoneType + | + | Set the pet's age + | + | 2. Signature : (Pet, str) -> NoneType + | + | Set the pet's name + +If you have a C++14 compatible compiler [#cpp14]_, you can use an alternative +syntax to cast the overloaded function: + +.. code-block:: cpp + + py::class_(m, "Pet") + .def("set", py::overload_cast(&Pet::set), "Set the pet's age") + .def("set", py::overload_cast(&Pet::set), "Set the pet's name"); + +Here, ``py::overload_cast`` only requires the parameter types to be specified. +The return type and class are deduced. This avoids the additional noise of +``void (Pet::*)()`` as seen in the raw cast. If a function is overloaded based +on constness, the ``py::const_`` tag should be used: + +.. code-block:: cpp + + struct Widget { + int foo(int x, float y); + int foo(int x, float y) const; + }; + + py::class_(m, "Widget") + .def("foo_mutable", py::overload_cast(&Widget::foo)) + .def("foo_const", py::overload_cast(&Widget::foo, py::const_)); + +If you prefer the ``py::overload_cast`` syntax but have a C++11 compatible compiler only, +you can use ``py::detail::overload_cast_impl`` with an additional set of parentheses: + +.. code-block:: cpp + + template + using overload_cast_ = pybind11::detail::overload_cast_impl; + + py::class_(m, "Pet") + .def("set", overload_cast_()(&Pet::set), "Set the pet's age") + .def("set", overload_cast_()(&Pet::set), "Set the pet's name"); + +.. [#cpp14] A compiler which supports the ``-std=c++14`` flag. + +.. note:: + + To define multiple overloaded constructors, simply declare one after the + other using the ``.def(py::init<...>())`` syntax. The existing machinery + for specifying keyword and default arguments also works. + +Enumerations and internal types +=============================== + +Let's now suppose that the example class contains internal types like enumerations, e.g.: + +.. code-block:: cpp + + struct Pet { + enum Kind { + Dog = 0, + Cat + }; + + struct Attributes { + float age = 0; + }; + + Pet(const std::string &name, Kind type) : name(name), type(type) { } + + std::string name; + Kind type; + Attributes attr; + }; + +The binding code for this example looks as follows: + +.. code-block:: cpp + + py::class_ pet(m, "Pet"); + + pet.def(py::init()) + .def_readwrite("name", &Pet::name) + .def_readwrite("type", &Pet::type) + .def_readwrite("attr", &Pet::attr); + + py::enum_(pet, "Kind") + .value("Dog", Pet::Kind::Dog) + .value("Cat", Pet::Kind::Cat) + .export_values(); + + py::class_(pet, "Attributes") + .def(py::init<>()) + .def_readwrite("age", &Pet::Attributes::age); + + +To ensure that the nested types ``Kind`` and ``Attributes`` are created within the scope of ``Pet``, the +``pet`` :class:`class_` instance must be supplied to the :class:`enum_` and :class:`class_` +constructor. The :func:`enum_::export_values` function exports the enum entries +into the parent scope, which should be skipped for newer C++11-style strongly +typed enums. + +.. code-block:: pycon + + >>> p = Pet("Lucy", Pet.Cat) + >>> p.type + Kind.Cat + >>> int(p.type) + 1L + +The entries defined by the enumeration type are exposed in the ``__members__`` property: + +.. code-block:: pycon + + >>> Pet.Kind.__members__ + {'Dog': Kind.Dog, 'Cat': Kind.Cat} + +The ``name`` property returns the name of the enum value as a unicode string. + +.. note:: + + It is also possible to use ``str(enum)``, however these accomplish different + goals. The following shows how these two approaches differ. + + .. code-block:: pycon + + >>> p = Pet("Lucy", Pet.Cat) + >>> pet_type = p.type + >>> pet_type + Pet.Cat + >>> str(pet_type) + 'Pet.Cat' + >>> pet_type.name + 'Cat' + +.. note:: + + When the special tag ``py::arithmetic()`` is specified to the ``enum_`` + constructor, pybind11 creates an enumeration that also supports rudimentary + arithmetic and bit-level operations like comparisons, and, or, xor, negation, + etc. + + .. code-block:: cpp + + py::enum_(pet, "Kind", py::arithmetic()) + ... + + 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/extern/pybind11/docs/cmake/index.rst b/extern/pybind11/docs/cmake/index.rst new file mode 100644 index 000000000..eaf66d70f --- /dev/null +++ b/extern/pybind11/docs/cmake/index.rst @@ -0,0 +1,8 @@ +CMake helpers +------------- + +Pybind11 can be used with ``add_subdirectory(extern/pybind11)``, or from an +install with ``find_package(pybind11 CONFIG)``. The interface provided in +either case is functionally identical. + +.. cmake-module:: ../../tools/pybind11Config.cmake.in diff --git a/extern/pybind11/docs/compiling.rst b/extern/pybind11/docs/compiling.rst new file mode 100644 index 000000000..3be84ba7d --- /dev/null +++ b/extern/pybind11/docs/compiling.rst @@ -0,0 +1,649 @@ +.. _compiling: + +Build systems +############# + +.. _build-setuptools: + +Building with setuptools +======================== + +For projects on PyPI, building with setuptools is the way to go. Sylvain Corlay +has kindly provided an example project which shows how to set up everything, +including automatic generation of documentation using Sphinx. Please refer to +the [python_example]_ repository. + +.. [python_example] https://github.com/pybind/python_example + +A helper file is provided with pybind11 that can simplify usage with setuptools. + +To use pybind11 inside your ``setup.py``, you have to have some system to +ensure that ``pybind11`` is installed when you build your package. There are +four possible ways to do this, and pybind11 supports all four: You can ask all +users to install pybind11 beforehand (bad), you can use +:ref:`setup_helpers-pep518` (good, but very new and requires Pip 10), +:ref:`setup_helpers-setup_requires` (discouraged by Python packagers now that +PEP 518 is available, but it still works everywhere), or you can +:ref:`setup_helpers-copy-manually` (always works but you have to manually sync +your copy to get updates). + +An example of a ``setup.py`` using pybind11's helpers: + +.. code-block:: python + + from glob import glob + from setuptools import setup + from pybind11.setup_helpers import Pybind11Extension + + ext_modules = [ + Pybind11Extension( + "python_example", + sorted(glob("src/*.cpp")), # Sort source files for reproducibility + ), + ] + + setup(..., ext_modules=ext_modules) + +If you want to do an automatic search for the highest supported C++ standard, +that is supported via a ``build_ext`` command override; it will only affect +``Pybind11Extensions``: + +.. code-block:: python + + from glob import glob + from setuptools import setup + from pybind11.setup_helpers import Pybind11Extension, build_ext + + ext_modules = [ + Pybind11Extension( + "python_example", + sorted(glob("src/*.cpp")), + ), + ] + + setup(..., cmdclass={"build_ext": build_ext}, ext_modules=ext_modules) + +If you have single-file extension modules that are directly stored in the +Python source tree (``foo.cpp`` in the same directory as where a ``foo.py`` +would be located), you can also generate ``Pybind11Extensions`` using +``setup_helpers.intree_extensions``: ``intree_extensions(["path/to/foo.cpp", +...])`` returns a list of ``Pybind11Extensions`` which can be passed to +``ext_modules``, possibly after further customizing their attributes +(``libraries``, ``include_dirs``, etc.). By doing so, a ``foo.*.so`` extension +module will be generated and made available upon installation. + +``intree_extension`` will automatically detect if you are using a ``src``-style +layout (as long as no namespace packages are involved), but you can also +explicitly pass ``package_dir`` to it (as in ``setuptools.setup``). + +Since pybind11 does not require NumPy when building, a light-weight replacement +for NumPy's parallel compilation distutils tool is included. Use it like this: + +.. code-block:: python + + from pybind11.setup_helpers import ParallelCompile + + # Optional multithreaded build + ParallelCompile("NPY_NUM_BUILD_JOBS").install() + + setup(...) + +The argument is the name of an environment variable to control the number of +threads, such as ``NPY_NUM_BUILD_JOBS`` (as used by NumPy), though you can set +something different if you want; ``CMAKE_BUILD_PARALLEL_LEVEL`` is another choice +a user might expect. You can also pass ``default=N`` to set the default number +of threads (0 will take the number of threads available) and ``max=N``, the +maximum number of threads; if you have a large extension you may want set this +to a memory dependent number. + +If you are developing rapidly and have a lot of C++ files, you may want to +avoid rebuilding files that have not changed. For simple cases were you are +using ``pip install -e .`` and do not have local headers, you can skip the +rebuild if an object file is newer than its source (headers are not checked!) +with the following: + +.. code-block:: python + + from pybind11.setup_helpers import ParallelCompile, naive_recompile + + ParallelCompile("NPY_NUM_BUILD_JOBS", needs_recompile=naive_recompile).install() + + +If you have a more complex build, you can implement a smarter function and pass +it to ``needs_recompile``, or you can use [Ccache]_ instead. ``CXX="cache g++" +pip install -e .`` would be the way to use it with GCC, for example. Unlike the +simple solution, this even works even when not compiling in editable mode, but +it does require Ccache to be installed. + +Keep in mind that Pip will not even attempt to rebuild if it thinks it has +already built a copy of your code, which it deduces from the version number. +One way to avoid this is to use [setuptools_scm]_, which will generate a +version number that includes the number of commits since your last tag and a +hash for a dirty directory. Another way to force a rebuild is purge your cache +or use Pip's ``--no-cache-dir`` option. + +.. [Ccache] https://ccache.dev + +.. [setuptools_scm] https://github.com/pypa/setuptools_scm + +.. _setup_helpers-pep518: + +PEP 518 requirements (Pip 10+ required) +--------------------------------------- + +If you use `PEP 518's `_ +``pyproject.toml`` file, you can ensure that ``pybind11`` is available during +the compilation of your project. When this file exists, Pip will make a new +virtual environment, download just the packages listed here in ``requires=``, +and build a wheel (binary Python package). It will then throw away the +environment, and install your wheel. + +Your ``pyproject.toml`` file will likely look something like this: + +.. code-block:: toml + + [build-system] + requires = ["setuptools>=42", "pybind11>=2.6.1"] + build-backend = "setuptools.build_meta" + +.. note:: + + The main drawback to this method is that a `PEP 517`_ compliant build tool, + such as Pip 10+, is required for this approach to work; older versions of + Pip completely ignore this file. If you distribute binaries (called wheels + in Python) using something like `cibuildwheel`_, remember that ``setup.py`` + and ``pyproject.toml`` are not even contained in the wheel, so this high + Pip requirement is only for source builds, and will not affect users of + your binary wheels. If you are building SDists and wheels, then + `pypa-build`_ is the recommended official tool. + +.. _PEP 517: https://www.python.org/dev/peps/pep-0517/ +.. _cibuildwheel: https://cibuildwheel.readthedocs.io +.. _pypa-build: https://pypa-build.readthedocs.io/en/latest/ + +.. _setup_helpers-setup_requires: + +Classic ``setup_requires`` +-------------------------- + +If you want to support old versions of Pip with the classic +``setup_requires=["pybind11"]`` keyword argument to setup, which triggers a +two-phase ``setup.py`` run, then you will need to use something like this to +ensure the first pass works (which has not yet installed the ``setup_requires`` +packages, since it can't install something it does not know about): + +.. code-block:: python + + try: + from pybind11.setup_helpers import Pybind11Extension + except ImportError: + from setuptools import Extension as Pybind11Extension + + +It doesn't matter that the Extension class is not the enhanced subclass for the +first pass run; and the second pass will have the ``setup_requires`` +requirements. + +This is obviously more of a hack than the PEP 518 method, but it supports +ancient versions of Pip. + +.. _setup_helpers-copy-manually: + +Copy manually +------------- + +You can also copy ``setup_helpers.py`` directly to your project; it was +designed to be usable standalone, like the old example ``setup.py``. You can +set ``include_pybind11=False`` to skip including the pybind11 package headers, +so you can use it with git submodules and a specific git version. If you use +this, you will need to import from a local file in ``setup.py`` and ensure the +helper file is part of your MANIFEST. + + +Closely related, if you include pybind11 as a subproject, you can run the +``setup_helpers.py`` inplace. If loaded correctly, this should even pick up +the correct include for pybind11, though you can turn it off as shown above if +you want to input it manually. + +Suggested usage if you have pybind11 as a submodule in ``extern/pybind11``: + +.. code-block:: python + + DIR = os.path.abspath(os.path.dirname(__file__)) + + sys.path.append(os.path.join(DIR, "extern", "pybind11")) + from pybind11.setup_helpers import Pybind11Extension # noqa: E402 + + del sys.path[-1] + + +.. versionchanged:: 2.6 + + Added ``setup_helpers`` file. + +Building with cppimport +======================== + +[cppimport]_ is a small Python import hook that determines whether there is a C++ +source file whose name matches the requested module. If there is, the file is +compiled as a Python extension using pybind11 and placed in the same folder as +the C++ source file. Python is then able to find the module and load it. + +.. [cppimport] https://github.com/tbenthompson/cppimport + +.. _cmake: + +Building with CMake +=================== + +For C++ codebases that have an existing CMake-based build system, a Python +extension module can be created with just a few lines of code: + +.. code-block:: cmake + + cmake_minimum_required(VERSION 3.5...3.27) + project(example LANGUAGES CXX) + + add_subdirectory(pybind11) + pybind11_add_module(example example.cpp) + +This assumes that the pybind11 repository is located in a subdirectory named +:file:`pybind11` and that the code is located in a file named :file:`example.cpp`. +The CMake command ``add_subdirectory`` will import the pybind11 project which +provides the ``pybind11_add_module`` function. It will take care of all the +details needed to build a Python extension module on any platform. + +A working sample project, including a way to invoke CMake from :file:`setup.py` for +PyPI integration, can be found in the [cmake_example]_ repository. + +.. [cmake_example] https://github.com/pybind/cmake_example + +.. 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 +------------------- + +To ease the creation of Python extension modules, pybind11 provides a CMake +function with the following signature: + +.. code-block:: cmake + + pybind11_add_module( [MODULE | SHARED] [EXCLUDE_FROM_ALL] + [NO_EXTRAS] [THIN_LTO] [OPT_SIZE] source1 [source2 ...]) + +This function behaves very much like CMake's builtin ``add_library`` (in fact, +it's a wrapper function around that command). It will add a library target +called ```` to be built from the listed source files. In addition, it +will take care of all the Python-specific compiler and linker flags as well +as the OS- and Python-version-specific file extension. The produced target +```` can be further manipulated with regular CMake commands. + +``MODULE`` or ``SHARED`` may be given to specify the type of library. If no +type is given, ``MODULE`` is used by default which ensures the creation of a +Python-exclusive module. Specifying ``SHARED`` will create a more traditional +dynamic library which can also be linked from elsewhere. ``EXCLUDE_FROM_ALL`` +removes this target from the default build (see CMake docs for details). + +Since pybind11 is a template library, ``pybind11_add_module`` adds compiler +flags to ensure high quality code generation without bloat arising from long +symbol names and duplication of code in different translation units. It +sets default visibility to *hidden*, which is required for some pybind11 +features and functionality when attempting to load multiple pybind11 modules +compiled under different pybind11 versions. It also adds additional flags +enabling LTO (Link Time Optimization) and strip unneeded symbols. See the +:ref:`FAQ entry ` for a more detailed explanation. These +latter optimizations are never applied in ``Debug`` mode. If ``NO_EXTRAS`` is +given, they will always be disabled, even in ``Release`` mode. However, this +will result in code bloat and is generally not recommended. + +As stated above, LTO is enabled by default. Some newer compilers also support +different flavors of LTO such as `ThinLTO`_. Setting ``THIN_LTO`` will cause +the function to prefer this flavor if available. The function falls back to +regular LTO if ``-flto=thin`` is not available. If +``CMAKE_INTERPROCEDURAL_OPTIMIZATION`` is set (either ``ON`` or ``OFF``), then +that will be respected instead of the built-in flag search. + +.. note:: + + If you want to set the property form on targets or the + ``CMAKE_INTERPROCEDURAL_OPTIMIZATION_`` versions of this, you should + still use ``set(CMAKE_INTERPROCEDURAL_OPTIMIZATION OFF)`` (otherwise a + no-op) to disable pybind11's ipo flags. + +The ``OPT_SIZE`` flag enables size-based optimization equivalent to the +standard ``/Os`` or ``-Os`` compiler flags and the ``MinSizeRel`` build type, +which avoid optimizations that that can substantially increase the size of the +resulting binary. This flag is particularly useful in projects that are split +into performance-critical parts and associated bindings. In this case, we can +compile the project in release mode (and hence, optimize performance globally), +and specify ``OPT_SIZE`` for the binding target, where size might be the main +concern as performance is often less critical here. A ~25% size reduction has +been observed in practice. This flag only changes the optimization behavior at +a per-target level and takes precedence over the global CMake build type +(``Release``, ``RelWithDebInfo``) except for ``Debug`` builds, where +optimizations remain disabled. + +.. _ThinLTO: http://clang.llvm.org/docs/ThinLTO.html + +Configuration variables +----------------------- + +By default, pybind11 will compile modules with the compiler default or the +minimum standard required by pybind11, whichever is higher. You can set the +standard explicitly with +`CMAKE_CXX_STANDARD `_: + +.. code-block:: cmake + + set(CMAKE_CXX_STANDARD 14 CACHE STRING "C++ version selection") # or 11, 14, 17, 20 + set(CMAKE_CXX_STANDARD_REQUIRED ON) # optional, ensure standard is supported + set(CMAKE_CXX_EXTENSIONS OFF) # optional, keep compiler extensions off + +The variables can also be set when calling CMake from the command line using +the ``-D=`` flag. You can also manually set ``CXX_STANDARD`` +on a target or use ``target_compile_features`` on your targets - anything that +CMake supports. + +Classic Python support: The target Python version can be selected by setting +``PYBIND11_PYTHON_VERSION`` or an exact Python installation can be specified +with ``PYTHON_EXECUTABLE``. For example: + +.. code-block:: bash + + cmake -DPYBIND11_PYTHON_VERSION=3.6 .. + + # Another method: + cmake -DPYTHON_EXECUTABLE=/path/to/python .. + + # This often is a good way to get the current Python, works in environments: + cmake -DPYTHON_EXECUTABLE=$(python3 -c "import sys; print(sys.executable)") .. + + +find_package vs. add_subdirectory +--------------------------------- + +For CMake-based projects that don't include the pybind11 repository internally, +an external installation can be detected through ``find_package(pybind11)``. +See the `Config file`_ docstring for details of relevant CMake variables. + +.. code-block:: cmake + + cmake_minimum_required(VERSION 3.4...3.18) + project(example LANGUAGES CXX) + + find_package(pybind11 REQUIRED) + pybind11_add_module(example example.cpp) + +Note that ``find_package(pybind11)`` will only work correctly if pybind11 +has been correctly installed on the system, e. g. after downloading or cloning +the pybind11 repository : + +.. code-block:: bash + + # Classic CMake + cd pybind11 + mkdir build + cd build + cmake .. + make install + + # CMake 3.15+ + cd pybind11 + cmake -S . -B build + cmake --build build -j 2 # Build on 2 cores + cmake --install build + +Once detected, the aforementioned ``pybind11_add_module`` can be employed as +before. The function usage and configuration variables are identical no matter +if pybind11 is added as a subdirectory or found as an installed package. You +can refer to the same [cmake_example]_ repository for a full sample project +-- just swap out ``add_subdirectory`` for ``find_package``. + +.. _Config file: https://github.com/pybind/pybind11/blob/master/tools/pybind11Config.cmake.in + + +.. _find-python-mode: + +FindPython mode +--------------- + +CMake 3.12+ (3.15+ recommended, 3.18.2+ ideal) added a new module called +FindPython that had a highly improved search algorithm and modern targets +and tools. If you use FindPython, pybind11 will detect this and use the +existing targets instead: + +.. code-block:: cmake + + cmake_minimum_required(VERSION 3.15...3.22) + project(example LANGUAGES CXX) + + find_package(Python 3.6 COMPONENTS Interpreter Development REQUIRED) + find_package(pybind11 CONFIG REQUIRED) + # or add_subdirectory(pybind11) + + pybind11_add_module(example example.cpp) + +You can also use the targets (as listed below) with FindPython. If you define +``PYBIND11_FINDPYTHON``, pybind11 will perform the FindPython step for you +(mostly useful when building pybind11's own tests, or as a way to change search +algorithms from the CMake invocation, with ``-DPYBIND11_FINDPYTHON=ON``. + +.. warning:: + + If you use FindPython to multi-target Python versions, use the individual + targets listed below, and avoid targets that directly include Python parts. + +There are `many ways to hint or force a discovery of a specific Python +installation `_), +setting ``Python_ROOT_DIR`` may be the most common one (though with +virtualenv/venv support, and Conda support, this tends to find the correct +Python version more often than the old system did). + +.. warning:: + + When the Python libraries (i.e. ``libpythonXX.a`` and ``libpythonXX.so`` + on Unix) are not available, as is the case on a manylinux image, the + ``Development`` component will not be resolved by ``FindPython``. When not + using the embedding functionality, CMake 3.18+ allows you to specify + ``Development.Module`` instead of ``Development`` to resolve this issue. + +.. versionadded:: 2.6 + +Advanced: interface library targets +----------------------------------- + +Pybind11 supports modern CMake usage patterns with a set of interface targets, +available in all modes. The targets provided are: + + ``pybind11::headers`` + Just the pybind11 headers and minimum compile requirements + + ``pybind11::pybind11`` + Python headers + ``pybind11::headers`` + + ``pybind11::python_link_helper`` + Just the "linking" part of pybind11:module + + ``pybind11::module`` + Everything for extension modules - ``pybind11::pybind11`` + ``Python::Module`` (FindPython CMake 3.15+) or ``pybind11::python_link_helper`` + + ``pybind11::embed`` + Everything for embedding the Python interpreter - ``pybind11::pybind11`` + ``Python::Python`` (FindPython) or Python libs + + ``pybind11::lto`` / ``pybind11::thin_lto`` + An alternative to `INTERPROCEDURAL_OPTIMIZATION` for adding link-time optimization. + + ``pybind11::windows_extras`` + ``/bigobj`` and ``/mp`` for MSVC. + + ``pybind11::opt_size`` + ``/Os`` for MSVC, ``-Os`` for other compilers. Does nothing for debug builds. + +Two helper functions are also provided: + + ``pybind11_strip(target)`` + Strips a target (uses ``CMAKE_STRIP`` after the target is built) + + ``pybind11_extension(target)`` + Sets the correct extension (with SOABI) for a target. + +You can use these targets to build complex applications. For example, the +``add_python_module`` function is identical to: + +.. code-block:: cmake + + cmake_minimum_required(VERSION 3.5...3.27) + project(example LANGUAGES CXX) + + find_package(pybind11 REQUIRED) # or add_subdirectory(pybind11) + + add_library(example MODULE main.cpp) + + target_link_libraries(example PRIVATE pybind11::module pybind11::lto pybind11::windows_extras) + + pybind11_extension(example) + if(NOT MSVC AND NOT ${CMAKE_BUILD_TYPE} MATCHES Debug|RelWithDebInfo) + # Strip unnecessary sections of the binary on Linux/macOS + pybind11_strip(example) + endif() + + set_target_properties(example PROPERTIES CXX_VISIBILITY_PRESET "hidden" + CUDA_VISIBILITY_PRESET "hidden") + +Instead of setting properties, you can set ``CMAKE_*`` variables to initialize these correctly. + +.. warning:: + + Since pybind11 is a metatemplate library, it is crucial that certain + compiler flags are provided to ensure high quality code generation. In + contrast to the ``pybind11_add_module()`` command, the CMake interface + provides a *composable* set of targets to ensure that you retain flexibility. + It can be especially important to provide or set these properties; the + :ref:`FAQ ` contains an explanation on why these are needed. + +.. versionadded:: 2.6 + +.. _nopython-mode: + +Advanced: NOPYTHON mode +----------------------- + +If you want complete control, you can set ``PYBIND11_NOPYTHON`` to completely +disable Python integration (this also happens if you run ``FindPython2`` and +``FindPython3`` without running ``FindPython``). This gives you complete +freedom to integrate into an existing system (like `Scikit-Build's +`_ ``PythonExtensions``). +``pybind11_add_module`` and ``pybind11_extension`` will be unavailable, and the +targets will be missing any Python specific behavior. + +.. versionadded:: 2.6 + +Embedding the Python interpreter +-------------------------------- + +In addition to extension modules, pybind11 also supports embedding Python into +a C++ executable or library. In CMake, simply link with the ``pybind11::embed`` +target. It provides everything needed to get the interpreter running. The Python +headers and libraries are attached to the target. Unlike ``pybind11::module``, +there is no need to manually set any additional properties here. For more +information about usage in C++, see :doc:`/advanced/embedding`. + +.. code-block:: cmake + + cmake_minimum_required(VERSION 3.5...3.27) + project(example LANGUAGES CXX) + + find_package(pybind11 REQUIRED) # or add_subdirectory(pybind11) + + add_executable(example main.cpp) + target_link_libraries(example PRIVATE pybind11::embed) + +.. _building_manually: + +Building manually +================= + +pybind11 is a header-only library, hence it is not necessary to link against +any special libraries and there are no intermediate (magic) translation steps. + +On Linux, you can compile an example such as the one given in +:ref:`simple_example` using the following command: + +.. code-block:: bash + + $ c++ -O3 -Wall -shared -std=c++11 -fPIC $(python3 -m pybind11 --includes) example.cpp -o example$(python3-config --extension-suffix) + +The ``python3 -m pybind11 --includes`` command fetches the include paths for +both pybind11 and Python headers. This assumes that pybind11 has been installed +using ``pip`` or ``conda``. If it hasn't, you can also manually specify +``-I /include`` together with the Python includes path +``python3-config --includes``. + +On macOS: the build command is almost the same but it also requires passing +the ``-undefined dynamic_lookup`` flag so as to ignore missing symbols when +building the module: + +.. code-block:: bash + + $ c++ -O3 -Wall -shared -std=c++11 -undefined dynamic_lookup $(python3 -m pybind11 --includes) example.cpp -o example$(python3-config --extension-suffix) + +In general, it is advisable to include several additional build parameters +that can considerably reduce the size of the created binary. Refer to section +:ref:`cmake` for a detailed example of a suitable cross-platform CMake-based +build system that works on all platforms including Windows. + +.. note:: + + On Linux and macOS, it's better to (intentionally) not link against + ``libpython``. The symbols will be resolved when the extension library + is loaded into a Python binary. This is preferable because you might + have several different installations of a given Python version (e.g. the + system-provided Python, and one that ships with a piece of commercial + software). In this way, the plugin will work with both versions, instead + of possibly importing a second Python library into a process that already + contains one (which will lead to a segfault). + + +Building with Bazel +=================== + +You can build with the Bazel build system using the `pybind11_bazel +`_ repository. + +Generating binding code automatically +===================================== + +The ``Binder`` project is a tool for automatic generation of pybind11 binding +code by introspecting existing C++ codebases using LLVM/Clang. See the +[binder]_ documentation for details. + +.. [binder] http://cppbinder.readthedocs.io/en/latest/about.html + +[AutoWIG]_ is a Python library that wraps automatically compiled libraries into +high-level languages. It parses C++ code using LLVM/Clang technologies and +generates the wrappers using the Mako templating engine. The approach is automatic, +extensible, and applies to very complex C++ libraries, composed of thousands of +classes or incorporating modern meta-programming constructs. + +.. [AutoWIG] https://github.com/StatisKit/AutoWIG + +[robotpy-build]_ is a is a pure python, cross platform build tool that aims to +simplify creation of python wheels for pybind11 projects, and provide +cross-project dependency management. Additionally, it is able to autogenerate +customizable pybind11-based wrappers by parsing C++ header files. + +.. [robotpy-build] https://robotpy-build.readthedocs.io + +[litgen]_ is an automatic python bindings generator with a focus on generating +documented and discoverable bindings: bindings will nicely reproduce the documentation +found in headers. It is is based on srcML (srcml.org), a highly scalable, multi-language +parsing tool with a developer centric approach. The API that you want to expose to python +must be C++14 compatible (but your implementation can use more modern constructs). + +.. [litgen] https://pthom.github.io/litgen diff --git a/extern/pybind11/docs/conf.py b/extern/pybind11/docs/conf.py new file mode 100644 index 000000000..6e24751e9 --- /dev/null +++ b/extern/pybind11/docs/conf.py @@ -0,0 +1,368 @@ +#!/usr/bin/env python3 +# +# pybind11 documentation build configuration file, created by +# sphinx-quickstart on Sun Oct 11 19:23:48 2015. +# +# This file is execfile()d with the current directory set to its +# containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +import os +import re +import subprocess +import sys +from pathlib import Path + +DIR = Path(__file__).parent.resolve() + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +# sys.path.insert(0, os.path.abspath('.')) + +# -- General configuration ------------------------------------------------ + +# If your documentation needs a minimal Sphinx version, state it here. +# needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ + "breathe", + "sphinx_copybutton", + "sphinxcontrib.rsvgconverter", + "sphinxcontrib.moderncmakedomain", +] + +breathe_projects = {"pybind11": ".build/doxygenxml/"} +breathe_default_project = "pybind11" +breathe_domain_by_extension = {"h": "cpp"} + +# Add any paths that contain templates here, relative to this directory. +templates_path = [".templates"] + +# The suffix(es) of source filenames. +# You can specify multiple suffix as a list of string: +# source_suffix = ['.rst', '.md'] +source_suffix = ".rst" + +# The encoding of source files. +# source_encoding = 'utf-8-sig' + +# The master toctree document. +master_doc = "index" + +# General information about the project. +project = "pybind11" +copyright = "2017, Wenzel Jakob" +author = "Wenzel Jakob" + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. + +# Read the listed version +with open("../pybind11/_version.py") as f: + code = compile(f.read(), "../pybind11/_version.py", "exec") +loc = {} +exec(code, loc) + +# The full version, including alpha/beta/rc tags. +version = loc["__version__"] + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +# +# This is also used if you do content translation via gettext catalogs. +# Usually you set "language" from the command line for these cases. +language = None + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +# today = '' +# Else, today_fmt is used as the format for a strftime call. +# today_fmt = '%B %d, %Y' + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +exclude_patterns = [".build", "release.rst"] + +# The reST default role (used for this markup: `text`) to use for all +# documents. +default_role = "any" + +# If true, '()' will be appended to :func: etc. cross-reference text. +# add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +# add_module_names = True + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +# show_authors = False + +# The name of the Pygments (syntax highlighting) style to use. +# pygments_style = 'monokai' + +# A list of ignored prefixes for module index sorting. +# modindex_common_prefix = [] + +# If true, keep warnings as "system message" paragraphs in the built documents. +# keep_warnings = False + +# If true, `todo` and `todoList` produce output, else they produce nothing. +todo_include_todos = False + + +# -- Options for HTML output ---------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. + +html_theme = "furo" + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +# html_theme_options = {} + +# Add any paths that contain custom themes here, relative to this directory. +# html_theme_path = [] + +# The name for this set of Sphinx documents. If None, it defaults to +# " v documentation". +# html_title = None + +# A shorter title for the navigation bar. Default is the same as html_title. +# html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +# html_logo = None + +# The name of an image file (within the static path) to use as favicon of the +# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +# html_favicon = None + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ["_static"] + +html_css_files = [ + "css/custom.css", +] + +# Add any extra paths that contain custom files (such as robots.txt or +# .htaccess) here, relative to this directory. These files are copied +# directly to the root of the documentation. +# html_extra_path = [] + +# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, +# using the given strftime format. +# html_last_updated_fmt = '%b %d, %Y' + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +# html_use_smartypants = True + +# Custom sidebar templates, maps document names to template names. +# html_sidebars = {} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +# html_additional_pages = {} + +# If false, no module index is generated. +# html_domain_indices = True + +# If false, no index is generated. +# html_use_index = True + +# If true, the index is split into individual pages for each letter. +# html_split_index = False + +# If true, links to the reST sources are added to the pages. +# html_show_sourcelink = True + +# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. +# html_show_sphinx = True + +# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. +# html_show_copyright = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +# html_use_opensearch = '' + +# This is the file name suffix for HTML files (e.g. ".xhtml"). +# html_file_suffix = None + +# Language to be used for generating the HTML full-text search index. +# Sphinx supports the following languages: +# 'da', 'de', 'en', 'es', 'fi', 'fr', 'h', 'it', 'ja' +# 'nl', 'no', 'pt', 'ro', 'r', 'sv', 'tr' +# html_search_language = 'en' + +# A dictionary with options for the search language support, empty by default. +# Now only 'ja' uses this config value +# html_search_options = {'type': 'default'} + +# The name of a javascript file (relative to the configuration directory) that +# implements a search results scorer. If empty, the default will be used. +# html_search_scorer = 'scorer.js' + +# Output file base name for HTML help builder. +htmlhelp_basename = "pybind11doc" + +# -- Options for LaTeX output --------------------------------------------- + +latex_engine = "pdflatex" + +latex_elements = { + # The paper size ('letterpaper' or 'a4paper'). + # 'papersize': 'letterpaper', + # + # The font size ('10pt', '11pt' or '12pt'). + # 'pointsize': '10pt', + # + # Additional stuff for the LaTeX preamble. + # remove blank pages (between the title page and the TOC, etc.) + "classoptions": ",openany,oneside", + "preamble": r""" +\usepackage{fontawesome} +\usepackage{textgreek} +\DeclareUnicodeCharacter{00A0}{} +\DeclareUnicodeCharacter{2194}{\faArrowsH} +\DeclareUnicodeCharacter{1F382}{\faBirthdayCake} +\DeclareUnicodeCharacter{1F355}{\faAdjust} +\DeclareUnicodeCharacter{0301}{'} +\DeclareUnicodeCharacter{03C0}{\textpi} + +""", + # Latex figure (float) alignment + # 'figure_align': 'htbp', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, +# author, documentclass [howto, manual, or own class]). +latex_documents = [ + (master_doc, "pybind11.tex", "pybind11 Documentation", "Wenzel Jakob", "manual"), +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +# latex_logo = 'pybind11-logo.png' + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +# latex_use_parts = False + +# If true, show page references after internal links. +# latex_show_pagerefs = False + +# If true, show URL addresses after external links. +# latex_show_urls = False + +# Documents to append as an appendix to all manuals. +# latex_appendices = [] + +# If false, no module index is generated. +# latex_domain_indices = True + + +# -- Options for manual page output --------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [(master_doc, "pybind11", "pybind11 Documentation", [author], 1)] + +# If true, show URL addresses after external links. +# man_show_urls = False + + +# -- Options for Texinfo output ------------------------------------------- + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + ( + master_doc, + "pybind11", + "pybind11 Documentation", + author, + "pybind11", + "One line description of project.", + "Miscellaneous", + ), +] + +# Documents to append as an appendix to all manuals. +# texinfo_appendices = [] + +# If false, no module index is generated. +# texinfo_domain_indices = True + +# How to display URL addresses: 'footnote', 'no', or 'inline'. +# texinfo_show_urls = 'footnote' + +# If true, do not generate a @detailmenu in the "Top" node's menu. +# texinfo_no_detailmenu = False + +primary_domain = "cpp" +highlight_language = "cpp" + + +def generate_doxygen_xml(app): + build_dir = os.path.join(app.confdir, ".build") + if not os.path.exists(build_dir): + os.mkdir(build_dir) + + try: + subprocess.call(["doxygen", "--version"]) + retcode = subprocess.call(["doxygen"], cwd=app.confdir) + if retcode < 0: + sys.stderr.write(f"doxygen error code: {-retcode}\n") + except OSError as e: + sys.stderr.write(f"doxygen execution failed: {e}\n") + + +def prepare(app): + with open(DIR.parent / "README.rst") as f: + contents = f.read() + + if app.builder.name == "latex": + # Remove badges and stuff from start + contents = contents[contents.find(r".. start") :] + + # Filter out section titles for index.rst for LaTeX + contents = re.sub(r"^(.*)\n[-~]{3,}$", r"**\1**", contents, flags=re.MULTILINE) + + with open(DIR / "readme.rst", "w") as f: + f.write(contents) + + +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) + + # Copy the readme in + app.connect("builder-inited", prepare) + + # Clean up the generated readme + app.connect("build-finished", clean_up) diff --git a/extern/pybind11/docs/faq.rst b/extern/pybind11/docs/faq.rst new file mode 100644 index 000000000..1eb00efad --- /dev/null +++ b/extern/pybind11/docs/faq.rst @@ -0,0 +1,308 @@ +Frequently asked questions +########################## + +"ImportError: dynamic module does not define init function" +=========================================================== + +1. Make sure that the name specified in PYBIND11_MODULE is identical to the +filename of the extension library (without suffixes such as ``.so``). + +2. If the above did not fix the issue, you are likely using an incompatible +version of Python that does not match what you compiled with. + +"Symbol not found: ``__Py_ZeroStruct`` / ``_PyInstanceMethod_Type``" +======================================================================== + +See the first answer. + +"SystemError: dynamic module not initialized properly" +====================================================== + +See the first answer. + +The Python interpreter immediately crashes when importing my module +=================================================================== + +See the first answer. + +.. _faq_reference_arguments: + +Limitations involving reference arguments +========================================= + +In C++, it's fairly common to pass arguments using mutable references or +mutable pointers, which allows both read and write access to the value +supplied by the caller. This is sometimes done for efficiency reasons, or to +realize functions that have multiple return values. Here are two very basic +examples: + +.. code-block:: cpp + + void increment(int &i) { i++; } + void increment_ptr(int *i) { (*i)++; } + +In Python, all arguments are passed by reference, so there is no general +issue in binding such code from Python. + +However, certain basic Python types (like ``str``, ``int``, ``bool``, +``float``, etc.) are **immutable**. This means that the following attempt +to port the function to Python doesn't have the same effect on the value +provided by the caller -- in fact, it does nothing at all. + +.. code-block:: python + + def increment(i): + i += 1 # nope.. + +pybind11 is also affected by such language-level conventions, which means that +binding ``increment`` or ``increment_ptr`` will also create Python functions +that don't modify their arguments. + +Although inconvenient, one workaround is to encapsulate the immutable types in +a custom type that does allow modifications. + +An other alternative involves binding a small wrapper lambda function that +returns a tuple with all output arguments (see the remainder of the +documentation for examples on binding lambda functions). An example: + +.. code-block:: cpp + + int foo(int &i) { i++; return 123; } + +and the binding code + +.. code-block:: cpp + + m.def("foo", [](int i) { int rv = foo(i); return std::make_tuple(rv, i); }); + + +How can I reduce the build time? +================================ + +It's good practice to split binding code over multiple files, as in the +following example: + +:file:`example.cpp`: + +.. code-block:: cpp + + void init_ex1(py::module_ &); + void init_ex2(py::module_ &); + /* ... */ + + PYBIND11_MODULE(example, m) { + init_ex1(m); + init_ex2(m); + /* ... */ + } + +:file:`ex1.cpp`: + +.. code-block:: cpp + + void init_ex1(py::module_ &m) { + m.def("add", [](int a, int b) { return a + b; }); + } + +:file:`ex2.cpp`: + +.. code-block:: cpp + + void init_ex2(py::module_ &m) { + m.def("sub", [](int a, int b) { return a - b; }); + } + +:command:`python`: + +.. code-block:: pycon + + >>> import example + >>> example.add(1, 2) + 3 + >>> example.sub(1, 1) + 0 + +As shown above, the various ``init_ex`` functions should be contained in +separate files that can be compiled independently from one another, and then +linked together into the same final shared object. Following this approach +will: + +1. reduce memory requirements per compilation unit. + +2. enable parallel builds (if desired). + +3. allow for faster incremental builds. For instance, when a single class + definition is changed, only a subset of the binding code will generally need + to be recompiled. + +"recursive template instantiation exceeded maximum depth of 256" +================================================================ + +If you receive an error about excessive recursive template evaluation, try +specifying a larger value, e.g. ``-ftemplate-depth=1024`` on GCC/Clang. The +culprit is generally the generation of function signatures at compile time +using C++14 template metaprogramming. + +.. _`faq:hidden_visibility`: + +"'SomeClass' declared with greater visibility than the type of its field 'SomeClass::member' [-Wattributes]" +============================================================================================================ + +This error typically indicates that you are compiling without the required +``-fvisibility`` flag. pybind11 code internally forces hidden visibility on +all internal code, but if non-hidden (and thus *exported*) code attempts to +include a pybind type (for example, ``py::object`` or ``py::list``) you can run +into this warning. + +To avoid it, make sure you are specifying ``-fvisibility=hidden`` when +compiling pybind code. + +As to why ``-fvisibility=hidden`` is necessary, because pybind modules could +have been compiled under different versions of pybind itself, it is also +important that the symbols defined in one module do not clash with the +potentially-incompatible symbols defined in another. While Python extension +modules are usually loaded with localized symbols (under POSIX systems +typically using ``dlopen`` with the ``RTLD_LOCAL`` flag), this Python default +can be changed, but even if it isn't it is not always enough to guarantee +complete independence of the symbols involved when not using +``-fvisibility=hidden``. + +Additionally, ``-fvisibility=hidden`` can deliver considerably binary size +savings. (See the following section for more details.) + + +.. _`faq:symhidden`: + +How can I create smaller binaries? +================================== + +To do its job, pybind11 extensively relies on a programming technique known as +*template metaprogramming*, which is a way of performing computation at compile +time using type information. Template metaprogramming usually instantiates code +involving significant numbers of deeply nested types that are either completely +removed or reduced to just a few instructions during the compiler's optimization +phase. However, due to the nested nature of these types, the resulting symbol +names in the compiled extension library can be extremely long. For instance, +the included test suite contains the following symbol: + +.. only:: html + + .. code-block:: none + + _​_​Z​N​8​p​y​b​i​n​d​1​1​1​2​c​p​p​_​f​u​n​c​t​i​o​n​C​1​I​v​8​E​x​a​m​p​l​e​2​J​R​N​S​t​3​_​_​1​6​v​e​c​t​o​r​I​N​S​3​_​1​2​b​a​s​i​c​_​s​t​r​i​n​g​I​w​N​S​3​_​1​1​c​h​a​r​_​t​r​a​i​t​s​I​w​E​E​N​S​3​_​9​a​l​l​o​c​a​t​o​r​I​w​E​E​E​E​N​S​8​_​I​S​A​_​E​E​E​E​E​J​N​S​_​4​n​a​m​e​E​N​S​_​7​s​i​b​l​i​n​g​E​N​S​_​9​i​s​_​m​e​t​h​o​d​E​A​2​8​_​c​E​E​E​M​T​0​_​F​T​_​D​p​T​1​_​E​D​p​R​K​T​2​_ + +.. only:: not html + + .. code-block:: cpp + + __ZN8pybind1112cpp_functionC1Iv8Example2JRNSt3__16vectorINS3_12basic_stringIwNS3_11char_traitsIwEENS3_9allocatorIwEEEENS8_ISA_EEEEEJNS_4nameENS_7siblingENS_9is_methodEA28_cEEEMT0_FT_DpT1_EDpRKT2_ + +which is the mangled form of the following function type: + +.. code-block:: cpp + + pybind11::cpp_function::cpp_function, std::__1::allocator >, std::__1::allocator, std::__1::allocator > > >&, pybind11::name, pybind11::sibling, pybind11::is_method, char [28]>(void (Example2::*)(std::__1::vector, std::__1::allocator >, std::__1::allocator, std::__1::allocator > > >&), pybind11::name const&, pybind11::sibling const&, pybind11::is_method const&, char const (&) [28]) + +The memory needed to store just the mangled name of this function (196 bytes) +is larger than the actual piece of code (111 bytes) it represents! On the other +hand, it's silly to even give this function a name -- after all, it's just a +tiny cog in a bigger piece of machinery that is not exposed to the outside +world. So we'll generally only want to export symbols for those functions which +are actually called from the outside. + +This can be achieved by specifying the parameter ``-fvisibility=hidden`` to GCC +and Clang, which sets the default symbol visibility to *hidden*, which has a +tremendous impact on the final binary size of the resulting extension library. +(On Visual Studio, symbols are already hidden by default, so nothing needs to +be done there.) + +In addition to decreasing binary size, ``-fvisibility=hidden`` also avoids +potential serious issues when loading multiple modules and is required for +proper pybind operation. See the previous FAQ entry for more details. + +How can I properly handle Ctrl-C in long-running functions? +=========================================================== + +Ctrl-C is received by the Python interpreter, and holds it until the GIL +is released, so a long-running function won't be interrupted. + +To interrupt from inside your function, you can use the ``PyErr_CheckSignals()`` +function, that will tell if a signal has been raised on the Python side. This +function merely checks a flag, so its impact is negligible. When a signal has +been received, you must either explicitly interrupt execution by throwing +``py::error_already_set`` (which will propagate the existing +``KeyboardInterrupt``), or clear the error (which you usually will not want): + +.. code-block:: cpp + + PYBIND11_MODULE(example, m) + { + m.def("long running_func", []() + { + for (;;) { + if (PyErr_CheckSignals() != 0) + throw py::error_already_set(); + // Long running iteration + } + }); + } + +CMake doesn't detect the right Python version +============================================= + +The CMake-based build system will try to automatically detect the installed +version of Python and link against that. When this fails, or when there are +multiple versions of Python and it finds the wrong one, delete +``CMakeCache.txt`` and then add ``-DPYTHON_EXECUTABLE=$(which python)`` to your +CMake configure line. (Replace ``$(which python)`` with a path to python if +your prefer.) + +You can alternatively try ``-DPYBIND11_FINDPYTHON=ON``, which will activate the +new CMake FindPython support instead of pybind11's custom search. Requires +CMake 3.12+, and 3.15+ or 3.18.2+ are even better. You can set this in your +``CMakeLists.txt`` before adding or finding pybind11, as well. + +Inconsistent detection of Python version in CMake and pybind11 +============================================================== + +The functions ``find_package(PythonInterp)`` and ``find_package(PythonLibs)`` +provided by CMake for Python version detection are modified by pybind11 due to +unreliability and limitations that make them unsuitable for pybind11's needs. +Instead pybind11 provides its own, more reliable Python detection CMake code. +Conflicts can arise, however, when using pybind11 in a project that *also* uses +the CMake Python detection in a system with several Python versions installed. + +This difference may cause inconsistencies and errors if *both* mechanisms are +used in the same project. + +There are three possible solutions: + +1. Avoid using ``find_package(PythonInterp)`` and ``find_package(PythonLibs)`` + from CMake and rely on pybind11 in detecting Python version. If this is not + possible, the CMake machinery should be called *before* including pybind11. +2. Set ``PYBIND11_FINDPYTHON`` to ``True`` or use ``find_package(Python + 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. 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 + Python, like ``pybind11_add_module``. This might be ideal for integrating + into an existing system, like scikit-build's Python helpers. + +How to cite this project? +========================= + +We suggest the following BibTeX template to cite pybind11 in scientific +discourse: + +.. code-block:: bash + + @misc{pybind11, + author = {Wenzel Jakob and Jason Rhinelander and Dean Moldovan}, + year = {2017}, + note = {https://github.com/pybind/pybind11}, + title = {pybind11 -- Seamless operability between C++11 and Python} + } diff --git a/extern/pybind11/docs/index.rst b/extern/pybind11/docs/index.rst new file mode 100644 index 000000000..4e2e8ca3a --- /dev/null +++ b/extern/pybind11/docs/index.rst @@ -0,0 +1,48 @@ +.. only:: latex + + Intro + ===== + +.. include:: readme.rst + +.. only:: not latex + + Contents: + +.. toctree:: + :maxdepth: 1 + + changelog + upgrade + +.. toctree:: + :caption: The Basics + :maxdepth: 2 + + installing + basics + classes + compiling + +.. toctree:: + :caption: Advanced Topics + :maxdepth: 2 + + advanced/functions + advanced/classes + advanced/exceptions + advanced/smart_ptrs + advanced/cast/index + advanced/pycpp/index + advanced/embedding + advanced/misc + +.. toctree:: + :caption: Extra Information + :maxdepth: 1 + + faq + benchmark + limitations + reference + cmake/index diff --git a/extern/pybind11/docs/installing.rst b/extern/pybind11/docs/installing.rst new file mode 100644 index 000000000..30b9f1853 --- /dev/null +++ b/extern/pybind11/docs/installing.rst @@ -0,0 +1,105 @@ +.. _installing: + +Installing the library +###################### + +There are several ways to get the pybind11 source, which lives at +`pybind/pybind11 on GitHub `_. The pybind11 +developers recommend one of the first three ways listed here, submodule, PyPI, +or conda-forge, for obtaining pybind11. + +.. _include_as_a_submodule: + +Include as a submodule +====================== + +When you are working on a project in Git, you can use the pybind11 repository +as a submodule. From your git repository, use: + +.. code-block:: bash + + git submodule add -b stable ../../pybind/pybind11 extern/pybind11 + git submodule update --init + +This assumes you are placing your dependencies in ``extern/``, and that you are +using GitHub; if you are not using GitHub, use the full https or ssh URL +instead of the relative URL ``../../pybind/pybind11`` above. Some other servers +also require the ``.git`` extension (GitHub does not). + +From here, you can now include ``extern/pybind11/include``, or you can use +the various integration tools (see :ref:`compiling`) pybind11 provides directly +from the local folder. + +Include with PyPI +================= + +You can download the sources and CMake files as a Python package from PyPI +using Pip. Just use: + +.. code-block:: bash + + pip install pybind11 + +This will provide pybind11 in a standard Python package format. If you want +pybind11 available directly in your environment root, you can use: + +.. code-block:: bash + + pip install "pybind11[global]" + +This is not recommended if you are installing with your system Python, as it +will add files to ``/usr/local/include/pybind11`` and +``/usr/local/share/cmake/pybind11``, so unless that is what you want, it is +recommended only for use in virtual environments or your ``pyproject.toml`` +file (see :ref:`compiling`). + +Include with conda-forge +======================== + +You can use pybind11 with conda packaging via `conda-forge +`_: + +.. code-block:: bash + + conda install -c conda-forge pybind11 + + +Include with vcpkg +================== +You can download and install pybind11 using the Microsoft `vcpkg +`_ dependency manager: + +.. code-block:: bash + + git clone https://github.com/Microsoft/vcpkg.git + cd vcpkg + ./bootstrap-vcpkg.sh + ./vcpkg integrate install + vcpkg install pybind11 + +The pybind11 port in vcpkg is kept up to date by Microsoft team members and +community contributors. If the version is out of date, please `create an issue +or pull request `_ on the vcpkg +repository. + +Global install with brew +======================== + +The brew package manager (Homebrew on macOS, or Linuxbrew on Linux) has a +`pybind11 package +`_. +To install: + +.. code-block:: bash + + brew install pybind11 + +.. We should list Conan, and possibly a few other C++ package managers (hunter, +.. perhaps). Conan has a very clean CMake integration that would be good to show. + +Other options +============= + +Other locations you can find pybind11 are `listed here +`_; these are maintained +by various packagers and the community. diff --git a/extern/pybind11/docs/limitations.rst b/extern/pybind11/docs/limitations.rst new file mode 100644 index 000000000..def5ad659 --- /dev/null +++ b/extern/pybind11/docs/limitations.rst @@ -0,0 +1,72 @@ +Limitations +########### + +Design choices +^^^^^^^^^^^^^^ + +pybind11 strives to be a general solution to binding generation, but it also has +certain limitations: + +- pybind11 casts away ``const``-ness in function arguments and return values. + This is in line with the Python language, which has no concept of ``const`` + values. This means that some additional care is needed to avoid bugs that + would be caught by the type checker in a traditional C++ program. + +- The NumPy interface ``pybind11::array`` greatly simplifies accessing + numerical data from C++ (and vice versa), but it's not a full-blown array + class like ``Eigen::Array`` or ``boost.multi_array``. ``Eigen`` objects are + directly supported, however, with ``pybind11/eigen.h``. + +Large but useful features could be implemented in pybind11 but would lead to a +significant increase in complexity. Pybind11 strives to be simple and compact. +Users who require large new features are encouraged to write an extension to +pybind11; see `pybind11_json `_ for an +example. + + +Known bugs +^^^^^^^^^^ + +These are issues that hopefully will one day be fixed, but currently are +unsolved. If you know how to help with one of these issues, contributions +are welcome! + +- Intel 20.2 is currently having an issue with the test suite. + `#2573 `_ + +- Debug mode Python does not support 1-5 tests in the test suite currently. + `#2422 `_ + +- PyPy3 7.3.1 and 7.3.2 have issues with several tests on 32-bit Windows. + +Known limitations +^^^^^^^^^^^^^^^^^ + +These are issues that are probably solvable, but have not been fixed yet. A +clean, well written patch would likely be accepted to solve them. + +- Type casters are not kept alive recursively. + `#2527 `_ + One consequence is that containers of ``char *`` are currently not supported. + `#2245 `_ + +- The ``cpptest`` does not run on Windows with Python 3.8 or newer, due to DLL + loader changes. User code that is correctly installed should not be affected. + `#2560 `_ + +Python 3.9.0 warning +^^^^^^^^^^^^^^^^^^^^ + +Combining older versions of pybind11 (< 2.6.0) with Python on exactly 3.9.0 +will trigger undefined behavior that typically manifests as crashes during +interpreter shutdown (but could also destroy your data. **You have been +warned**). + +This issue was `fixed in Python `_. +As a mitigation for this bug, pybind11 2.6.0 or newer includes a workaround +specifically when Python 3.9.0 is detected at runtime, leaking about 50 bytes +of memory when a callback function is garbage collected. For reference, the +pybind11 test suite has about 2,000 such callbacks, but only 49 are garbage +collected before the end-of-process. Wheels (even if built with Python 3.9.0) +will correctly avoid the leak when run in Python 3.9.1, and this does not +affect other 3.X versions. diff --git a/extern/pybind11/docs/pybind11-logo.png b/extern/pybind11/docs/pybind11-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..2d633a4d0c129d6bdd94b4134c9516fdc92bb192 GIT binary patch literal 61034 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 literal 0 HcmV?d00001 diff --git a/extern/pybind11/docs/pybind11_vs_boost_python1.png b/extern/pybind11/docs/pybind11_vs_boost_python1.png new file mode 100644 index 0000000000000000000000000000000000000000..833231f240809884fb6eb4079db528b9b3c0a9ac GIT binary patch literal 44653 zcmcG$2UJsC+by~S0ty0#-ob!0=^(vo5G*tyf`Ficf<(IX7WG9sm=~2^M5TnLV(3kf zqLhFLNUs_YA@oSUEBJlqd;fFpx%d9}j=LSh0TGhD*LupF^O?&iQ)2^0dLDWRf*3Ct zBCkLYH35RC7?09|SD;a&cJMdttxE<-=z#JkyDl#gf`p(8NIeVx^j~8^!M3+Ebe0L^ zluJApUr4)3+^xi8Mvqunu(97i?d46A)QB_va{ou?doHD#>gxSkJ`bPb!V!I?Pw5v% z+Vw4+*q_BrvXAM*3&`}ZM~5FjMT=o`@BXbuAd}lVpWc-*`XZ{9*iGCETyaq#su8z3 z9gW-lGKrdWLWuvym0BV)2O%v&Q&UrML4kqsjNxY`5dPa2N(3|6pE2q+eA!gWb95G=j()kE$JV(bGl z{kk3q5Iob{pw9OL`{!(IMjp{+K8cJJSDnhHK4G@Z5`}b-NwQ(gu3qkeFs=|2%^RpO z4XXX*nI86QaWQ0~xMF;m)%5FylZc1{7e-9LBV}nS2<^JAyW??PWprq0>l$?MwBvg` zwW)e)m@0+`r8TX)lj1yh-ER!Ox3{NK_%lGfR^ZeRSUyY+{WFoq05v%MtZD1mtj>)s z2ahPx_((pc8}gVu(YYsQpgYlq{YUr{!0pLNWkyNbBCx-caiK8CVgm-bSy~Pf6jNfE zXAOodHdt}h^o=!zg@p}FlhKf{i7I+A3puL{g(Vkhm){#w5Mzl74*sjFf!ZT2a`(== z?FsuT+ocV}sy3AIs3NM}1k%jIg{5pee#kE@jKTe!fMIZUNyd3;kGhfIGJn*fXhsv$ zHdAWZPvETIYt7M*De<|kVmX|P$8T^$gQr-cI8hsY(EdrRVXelqi(_ivABbI0&)A1y z1dsGL5CgcOlx*;!nZywJx#Q-p-RWu(FP0CwJye6;8$BZy(C9YzNPbRJiL5S!>uJGgmd;cdW`{ zh1cYvc!k!#it)1jU%zVps4mI)E0hW5@ec2sE1wG@POP=s-N9kzCfGc&t$M+dl9K&} zE|rPhim2`i*|%1|4B^+PvV(ea^wVe?2p}FWv?dE%_>>U?WaE8n9Tlo{QAzo@MG|S^ z>-hM%Pd2w@k(KAeNIrI^2*1tsbxq|jua6B3Kar<}CU-(f`4dcc0L=vvtXfv78z_=%><*6pqQ`ExIN9HgbChlIRNz~OO^86Lw?`HvqzPW4b7 zh3Q&sG|%7P&h6cdYMUXkV1!!Drk`be3k(+x?Pxc2zBZ&v+-gJd{#3^HI2 zOtGU{?gw=z>@-JI{LCoRgEZ4RzK?T7A?u59gHW=WvjQZ<8igzoT%KY=7H5@I#UBVu0g;qTwA9v{7!)?Ebw6*zNRnLo&;443eWC3+3 z(@`Oy1g}gp`TkZGVl|tzLvlx>dA+(^H3h;HF|yI^X)B+1K5}9Oc^tIAeEIV3u06qt zizT7W0kK;8EeKmXhFImFAnVu#hk<7%M4o?{5l-~I0Vhv{x7|DtyrKMTdQK$`l)OFfye`zlj3_)H@^-=cpGc#o__MVwe-wk=hKr$@xWFu2=&R!5 zMeLNF2`Kv6$-QYOdOwJ>(JyX2!Y|Y9<$sx94*|LF;4$?}wR@x(&kU(c*sK*V~?**#Z zAU>K&JCrM`-<|54b^BIIcXu}->DSs3jLxDOracR~_hx{@;Ic4u#|lmUslySIaVvI< zA48`8#yHskd!PUF9K;dRZr$d2<@HYT?wdJ2Xsu~#`8tk=Zux!Ws9F07*z&%uj33?k z18BnpQs%4|fZo6wAJ^B{heG1B8D^@YkPshJ#FK+60mjLs@TRQ;Xf$q@jscI`9`o1S zhoKh<5Fg6_V2^L>>9~>379-C#F zNO#euu(2aqjYu5hzK^(NF_xVs8^Jd$rX8uOw z^pl~G7~42eho*;lasvvT1l<*Nh9=t*)sV2e;Er^>W@17hUSyuDcJtr-H8dd+Y-K?` z$r-*op|^jq7zK)I#I3hhuJfTA8yjUWd*@{7NilpF_mg2I+RQi3tQOWYzraPnk{x2X z@2=Fk%ue_z8U#o~(2D=AmqVwl#YQny9^JkOX+k5&*;@{sk0`P9mM;eM0G!9LvRSx7 z>sOp4v>uqLG7hiN`nj;+MEzTE_P~GN&fT5V-y%_3vXJVO8So&%Ke#7Q;xb|BMI9dR9`RfeKd zw>4x*9Vr~Pb9zIQ)Yp+Dnk!w-oGBpj$`7*{Z>e+-5qa6j2W)zxUB<8RxN!<@lw5BO__`hpZ&H00JK1lc37 zm8Ph$qm!oyYvXFUoDw8QNY)xXVHzAqU&g*DHh;69*0bt)=h6d#{!%-z&JA38$$OO z!il>w@G8#60%)JV(Wz|c7h}sM4!*XkV_WbsX6Sd`^aHS0DGAZ-LC|`1J3FObPM2Fr z`PRz+u!F>++KZuayHSp~^E!`q)hZYpM`HR8_}ii&XWA3IZEHiar>_>NUPab32}vU6 zK_)H4^mShH=5nY9&HBEVY2T-)$Vl3mc15#r$;Fn_M_+ zd+Tp((O70Gf2KxHEiJ7i=86et>+DV+{$T&qo^-?|yAb~|h>uGi_3{Y#&P0Uw-; z=Vy0&5tgd*_3eSC`;$Z{+apeiUQH)9f70TNomBdR-Fr;4PNKR`0E1$n@@3eG`jk;yP%KBKuTxMpoGH;V8~Z**Q)~v zYqal%e)+UWC4S=a)>^Gl*?EbxvkVL=Ck9XhLKW=L53dr!E8F=N>Mv1%V#<|U4;96^YQ zJGa9KY?b9B!F5gdq{!Vj%ZuU7xB26U0a=@x?k|V|pQg$SUFH`8=JwakRJ&=yWy8LV zj@G(XDh~`#F9hbys9vIVmmLkf*WL_e%G}er2NpUU$8;^OdX~67dvws2Q%6L+j<|U$ zJfuNuG{-j_`)N98?%{_Kx=W&oZ7|hiP^|pIV|1DC;;=Mg^<`7V&W0U#>*g2csEhX@ z-J9zBcA$oA20P!1O`(JM`T3oM4<0<*B$JubbTW(&V2#ZH!R?1bZ@h`!6(@Dhy&~nm zjIRz_)(&S56AkJ2gIB%!_8P3(qYBRGc|nfh`0X_j1R_;3z1+y+1#-5kDQRtU$m9Mc zNEdaWdW9OjD9CV{jWD;kSPV0*=6dSu=f|wFf&Xo=VOZ#sP^qAbnMip5!aoFulMyKB zvQmv_g=5Q#@p&=rg-^0_cU@*VzfnE0PuYU1#W|XV>q}xdqyt}-m6Zu@g@1g0qbL6b z&W-=(;PvoD#q-~^PXVy7V4P&_Q*_0+irP@pFe+|g^Z~9y3?hy7* z%`8}WS!BC6jvk((btSEr?W)PXB}%J$nTJD57%_3R|1_EWO&=F%HT6wcn{&{oct%yL zos-ZU*O*XAvhd&fp5zzz-U!*c?kzoPSv40c|I6#QGh-3HWw6}IzJmR9fFL7zcqsvH zpKvxzIFs(tFuQ$X$aoV#vR+dG0$0A3{>}^weHCJ{k@D8@n>}a~>EV`l>guYOC+cJW zx!0gZ5T4k7;BHNpH;Uuq;;Ph4>TvbpBd$0qT&sQ7Z48~j!t=yI@6e(#6=C7lMENyE zR2AVF&JSNzKm~l=ImVp##>HQ8wIYA}6o!|+;u~&7v-B!b`|3S_Z8vv(F|EnBZYoVL z{H!jKaH|-I51d#i@EyLJXY`TaYx1E0M*?lpo4jFse+MjN7rP^(x ze$TPiD=iWwtEWm z6_&mU`8GYw1vWWRp>-M!gW41u7hG5`cU z8NYccT-N>+BCAb%X=g#Tn~s6}EgZ)|euUevT>g#gF3C@P2;b(*Fm>oJZC93L43HM7!wUDoV zSKIaU^q4B1E&CoGCg_Sr2f6&EOovMj`r4WJ`nLcpmK-N{MG6XKVTw3V{#`%VeE06% z0G4nn3sfn8iIGBzt%dDz6XX(iiIFLIW&zqa)Rj;Qf*uoiH;w)D)lMHir!$x|`o+*%=iVHf=n?NKTV9L| zga_?*0erwE0~>%$c3CkUPa0giyAt=(=`XAD)#FX2sM`-72*#Zsq)uj>WCnGO=N-$s zGp?}EFI$evskqL6*|*J82Iwz5bwulH6J+vb%QLO9z8PZi>L9RBhL!&0p7Qu@bK0^b z-Mm^0;IT)z(EQ~^%hol`)^o_{ssuyP#2C@SEVClm6Z8H-A79_l7N4;%*UbMm6w#)e zygVxl4I!fsxQxv>V8M3o^4Yv)0 zS2bA&h0mx0+K|yZ1sR{a>GCFWR~A$KMgRFgsr|uOO@W5<$bGs-o%1hyz4_a!7`7G) z2;g}UVO4p6kSEZQbuQRl?N>p*>@@)Jv;_A9IXl)~I=Pb*`ISUC`aAUuI*>K$qc!R} z&E(4{nO=%>hhrxST%<9=`i=zr;Ns$94S!nzed7vZmAcI{lM@>TczVV0 z!K8)iDi96N@S9@L`1H=cL;TFHF4C8cKIYbk`-Q}S;%0sagcVxlq|P?qy8-R*s@+WR z087bdJf7^!QO}L@4klHNSyu&FYzK#i0$$9hl5sfc+NBm|VYhWnl8S>`E-}DPQ$WWA zIi81`@Z=*Bp-{RTOsg+T4IqC#WQk-QvuD)_Y#5z~%xcAt?I`dDu-^=rmqmDz-oEx{ zn~;exor?qP`8eD*d4GT3FaM`aoIF`0evKa*L2VQxR%sz#tPYWUtJswTi-t*oTbx$y zu7mCY_M`!In)ka$w4i{>K{2P)1gq>`S4uPvNmr5NcZw(9#Ou1E@ zM2lw+LL^!FVwP}c6OxgKa2;@C7G%kej z6Vkago{baK1GjP+#ZOp!ev^gc`sr0ol0I~BB%5xF+=Ruczp6~V2WUPDC9Aiu?05;X zwFF8D&z4PAe^4ede&$K4ah?t|c@8QUK1khH#Lz;Du+clQ0bLfMhH?Vo8^>hWu_mx~ zDxeJ*z-;4ocRFH_h*Eydi~|aJSy))8fx>%(Arjp(0payLa`eb#V)F)2DPt-Y1KGBk zYMB~QP#~;!kZY}YW!B{Nm10PS1ikO+=IY}9R~JQCE}&dt%Qk?Efyn#F zwU%ltV@Wgl7Vu5td5u=9>)u^w6bo^<=WOSbDrBwS&tXHxHuKwk#+J(n+BPw}T8}Fd zZVOI6%eo}a7R_}0`2C{mHJisS-|E%0a&U0yFS%Y5&w))6TlEYlb}aDNm1}0(dD_a!Vj;whv451uU>Hg^m{+NV%EKiv|~~~`sdaEzyI!i z^>;S)(=>n0dNZUKUl8m+8tr1qvF!5>kkcY!}(HOU@^HXLILG^YNowPhhwq zT4H?sP~^ElkGK^Fq9 zP|zu#xz~#IjKb|}VRWL~39V`KH+-{6oTxfsHu@FnV~EsHzP11$n$>zsZeJ8lbQdSj z2(-VY3{HT0@@dIiY7wN96VT0H&ZuU6%eqXb*yhP}K{PRs4y+iZy#rlEF}Hf+(H`H} zM2Of7-*vo8e-r3--~Bc18gx>a?f|`^hys{;)HY8=#!@tcIn{3YM9*JQ>IR9QW*>sm zK59YaB7{10amBP4Ul3ujHx^+b4S~-pqYi0MjqmHHKp3-tNaxizk=fBb}g$P#)B@Bni( zI4Y#P%lyKPY^7n~(9G((la&(dVDdG{cd98|wtoG~rIB3qHvdhHp4g8Qo=A%ixn9G{ zM#axgTgv$IIob87MLPQ@o-xXd6s0Bfe)@2C1ldOiKp+@qfb0B+=@iMc=H}*j(vBx2 z_~YSCR%{{6RGu+G7A{tz7HXW#WG(a zEqbjq&GoIy?ijgWzdq&yEo&wey0Sh?0SVk4$E#A3spc(8`e1DF7;FTPgIJ+|ughh2 z;lxktv>6W~A6r_bWD&*9Qa(E>X#2;rn1#>avnVqiaJ{^-a{13!O7zAfxzRlo*?nXr zh#mSmFfe{K8F3ML`_|U#_cb6f=_6LXKzk9M(?q^kY4ZdzN&q~90ECV#T0s59k4j}0 zApUB;=@5I3?>-ZP$4;CLr_d^Owf|nvwMb`iRL1StYxj!3gLSZYOL?d{aAj!)`OBF`}%Kd#9ig;W@@9xriuvNo0#k)T{t!qM^!GNwL3wOk2J zR7Ct$XQQ)Sl8F&!={<6D$`P#YME@L~ln${EI%yQerhbixS1VY(AK z@z=ZSH_LQ_*9ITZiAuApfzK=gk<@Pts{p%EamD<1r zn6k2bZk`i$?2UB}6Xyg=)m=Ui#H2w*Jw^#UQ_8$T#eMuO)I%A1^?;h-4jQu}0QKnL z2|_?-#TiQ6Re*8TH89Xy3z;BoXu1dW1blvQCij!o&K)Qs;mg;GCn&q=Uk?h}^^u7Y z!UPMr3mW~ho+P^#5|*-19XtUN$3s<Ow9BUWk!&#FfE&KLRlo^;d3g?$6B3%mkROkx^G%$-;29 zBE0uBko}A0MRRdO_titp4>t)k0V#OZWt2)@-~7mx3oe{TCWg1HnVy6PpcGPm(E^G|(jv@+Y*7N^D3nblMt)NLolVTmW zy_JrvB`OS%i7%@1Wzsx{#6 z;)!01TYu`Q64R~O`4!Ub%+zHeq|$4U6_RHG#6yAk1qw2?u?ga}91)e2J)9iShXah7 z1cY~ffVXn$)Z)uNg4`Bo3lx2JaZhPZQ*X^^DXEp&k6JZ2wkH4B7WwiWQABDmUqEj3 z38siR`8#jr?|A+zm@QDMItj-pt>rbYELJ|I7|-yi`wCOM{rX>hk!SlE2I4;G6H#%GHeJ&QVa3lUsg|yHkWe;Jp9b%ky4a zj;!v6*vpEqM#B>aA3m8Nk%@7Aa+$^K4*Uf%%x*>*Z(1LJugg(o z>^Z?^V)cx~PrA2%u7^9ydHT(tC|8HtLr zQ&|DI>_!_UE7po4lJ9wfLOub$@QHrIwIAztrl1&jZH2kQc%90KY>T4?6MPZn(!8oPM1adm?ygro0GM*^}9*6ix%a8gf}oY zmV+1JG*Nx!|G*8DxB~T(jnY$IPyoLBc&!7BqBVWWUOUk{PJw3@!2R*NK5I?W9etRH zl;;KV^pBnT2|(UA!Oy{I9{e|JAO%su_1I6Qb>VP#P-l4Wb0BymdE$Q16P^4hk0y>< zH198xzvBeHiG0{}cfVftnw*Tg(>JeF8Ui}k|5R+wA=!=q&isdT z#66@uEEst9f!TKj52Q($$yYQ>DF8$1(Mr#mpDBt?E{EF#{2Myd-;Oj=5T%(ZKq4(b z+pE)a0{x9$<~3z!autB7h628({?*PXdjae>f41d30D2a19$wzUz@VT~Dh#L~Bb8um z8E~yWcx%^G{603aHr+UnbX1)gzS!lNerjOY8^9+;!0PiTjEbI$60uVr-?$t&=s*NN z=A;kDfn5F9dS662aXWC>psQ|h!BJ8i;F;9b)wi7pnHjK6DFD`Pf8;7Vhf-`!-Urdu zYf4Q`ZDN4H#^QbdwJ8+TdGlFM++o88eUm}cAnNngpr9a5Ov#@O0ltraFWG7$=SFN~KJwhT<|5$H$&LJF{Q3$r9VI`WUl!%GLYrLazo(w@;}i$d z0;{?T&{s{32GtD-%>snfbB5dr=5psFh z_TM^m8F7)$l7H$*PDu5S$2C`v`iAJSuLRIgE_=9ICM*{yqy1#YB3 z*#XM9V(mwl1_uO^H}g1UV~fE||2*hCaikqJ!df{60o%R(^|;3E$gKH}v)2XVCT8h_ zik5SCyOlBap-RnOItviwMgn+QSfx zJMRr5=^Av1Xe_NBFx)7r@Yf`#@{;N&uTIuqhy{X4@?A5{Qfs`+O_?MOmc|(O(eBsyAJH&*N=*sEB1`>yX^sMh3CaQ|gxYidwd#@~|`%9JAU=;bv4$SH)5(o|1d zhAoZ1e6ltnml<-zpP8a1=($+45$Z_|<;&k)Gl^qwO3Q(63g5CH^$W~QlF|-cy(%m@g<%edz${B zL^@=5z{Y(M=D~nDcPY>NFi1=}QhCY7(GM;}o<&-4p z#bE0RuV3fyl8cv4F`IY^A#xg@hODG_e7{qGqnqp)y0__CVBu0n3`nt%+_(wENC>`l z>-zQU@84WHqC4TyrqIe3wiO|&qxLKjc+J-DlnwO{-Sa(TkP-FS1jMkDa@ut*Ik_hk zmRmRd+{Au2D6#}PU-x|Lh3N)Hksa`XhgUZH%C!h1OgFxJj9e-BN(K_M7G|(BLxb!C zg)-jY#B71CetmEZ{eoeFoFLb#i`*|ykP*ghcLM<;!#FR@wE84Rj5IX{^xMUz;sgka zp8@UtI&h^?X8X^lXMiSsd4fz(YP|w;iRtM!Szr^w%y zGC+6~@+RTY+l2V|c!oQKOs&N%x}6(>bhia<8Y1e$Iqk)6Qvmg>TY# zTEX|3bl4R61H#~a z+qC03VII|yLMfNEZK^+52hn0O>Pe)HOU5X4tb7}z9iuJs2j~ki?J=(t2+8sekMX{5 zaoReS-VU9&pc{g}z-=;be$f6#w5d$f1Q2dAot#1yJVR}BKO|TiX6RNyr+*wFSLGPd~4N7hdFiW`L8q5 zqvSn%UWa|YD5Ux&K@y1+*2%T@PX0%@UP6OoY!#rr4kyry8UMKP8-m}zw5c&v2<(;X zo9xOWjiV=@T?n~36Vcon7C|ErtR6D$6d&Whe@{&(L1Av@)AQ%g@2#u=HpJx6=^iI< z$*9c%f0nm0_FEf@Lf)#vm9SzpK>P$)FY3hx9tCWm!0U~FW1>ggEB2k_KM=gKP?TEBqQKiOwZqUK+SB577 zzAdhL+g>v$bI`ST&j6B*X%7Ss2FA&j1H)qav%&DYBtU^~y$$j9UNeSQabSQArrk50 zj<0R&W#ld=rnsKeMjfQUmJDJ)6xEgQ^7v&p@_XCAP)5*0AjbqS=h<<>vqSl6I-lCk zQ71kFa*y-vur5XDJs!VzuOElX+o^B3f|`J`c^df)lO@iZv+RTsva$MjZF59&7ZW)Y{0%IUx@Et`EP7E?pUai2!8u;@2Ocy7y}bV=}R| zCLvl|Bk(jLXa4SRtR^e}(LtZ)jEoE-gdqknLp>N_xLFiMPXM#P%kZI$i?0QkKxc=w zsU^4$5BoMLo{-{M(5BiAR#1ckfKEY||6vMlLlZf1`CtAi#fY06p~sfShR|tF#4LxI z+!8_L=>CggO~tg+!iOfSWhnLWvIxsO9Lw06+;9X?3cHnuVc&S52EKFV4E9&kYj5>+ zBxXbbra{5}CxL`VU8#5ou0a||0rl=#Mj0zFZMl-5cM9@l#c7Y;qh0&7Ezu4cU4BM) zRS`K7t$)Op^Y?$@imySO?kmQ%Y&nCv%REpS+c!jGh}ol3f&S&+40=QnMC!disMgsc z9D6%nhSjVE@YnxujDz8^nSOq8aol!&LnqK#N)}c_Of2pIc%wN%PAd5M{g6`uSKQxC zYTNOD_|gAEaRne}dXk?;x19fBytp3Vp?3{-04ak2Rdy(kOu`0BLQ?uVrv|)B_UnS{807;fO3z! zQ4|XO1Cs%kKQEd%&!|of0I!Dx4Z$o&;n^q0V1S@<=-Yta{O9_=R&iVV%cDMbtbXqx z0!K*RrbxggQht$ABawW4k@j%U_)(%CQKq zL3uhhHVT%eFZ|2x5Wy4HYOf#iV1N#5p!+lchl(5iON9MDAzZeH-jGacD3hoK=~Bld zk*5wvT+LI8`i_2JXH^dcLNL&3v<~|FkTc4@70QYJNZ}e|V`FgW5=_eHy5-?!DYk+$ z76tgt(Zi>zCk+Z69Q2k^N*x>9q2xL@w@mELsDT0Z;CBj*j6x!`28J0C?@oicgOY_z zQji2soEx+p{69A8EZ8VI52wSS05I7fqFkGW!O{uZ8#ds@$cG>QH~S?ZDS{{k6PhvT zM)4j%T-16IIn=;(`TtH2&8_*TWF{BmoO)wL^JytA4YbH}dUP+M%x!FV7w6}%(LyB) z<8##|jOwiLIs_jRfg+OP?!ZRb4y6)tHfu1E2jR@!xTr@=Svu6oLKjt;4n$9;ic+Ga z{Z1KA6!P66&jbSUXk();hb~1X7(a0L6XbojElSOIN7}AaFKo+*JSIzLNTOiqjO0*R45DB%hg{IjfruAu&fk+WR(ZRfHpc z&uyF7?w;*Ydt}Nb5#spuF%n59Z|9%~Qh=TGla~}&$iBvFW~PzUE4z`G2heJSD(sapxaOXcs!`u1l2NnflF2mc~IbvC9F`|ELAtcvl3@ zkS_o*hMegBH`_a95T8~_hJ|rPx)Ks;(M{pbH~;TsOv#u<{K@u8zrxCftp7UcDX6sH zx2RG`x$NG<4aQO7mn8h-zdS|z$&##no5y5t{TkFfr!SE8(eSoJ;-}vr-;nI_hgws9 z&avgamBpKFLz$70OtgoMAU|h|XPXK$=5TNP!B4|L1)w3B1x!|h=Y5BEM_VvM`;%wE1NIT=aK0hFxNp`p{g(t|?tSqscj#3FJd0iU2`}He6Z2h{H zIY-H5)&E)91ai|PMQip@y$AGccH*$L)Lh z`fQqCDhXMCo2^KD`^JsQc|(!E-wpto;oq`@co9prJf#|H64rblfFO0AsW6EV*!DW; z>bc$>+f$;IVDPpkki+8?t;g2$a#o#yd-v8jsbjg5H#awBpz9z-5cDjE*u<uy0Ea58%aiwJ_+`0c#zy#m`rXVrRf%ooA;gLz2mA zw@SdO`M?yA1cP`yL=cj_Jw0-#`IxdK5H)~fHtP{ze9n;^OqdB{xXHSj2x)6ZVLu!H zud*j))t8o*j$&Rz&#OyL9}3Uh{~0UgzniOoQ?0uK7$!tCxsv|w`%Wn1>VcpZrTQWD zQl7VXQ*@!XPKt3}#Hcz#Ai-V&kwxX< z<>Vv=CfYq*Yb!uHq03cOfA-*iRaIegAB&c@=E}JyFir8FKAV^PQ)Ins9ow!R&w6e@ z0XG=g9*EkDjT~jpHbx2nXGb2BOT!gpJ< zvS8hTciVYhnXpz*E5tBKiXJ=@(L9BQ@lj27v;b>6g|%61_(FLUTcor(`FK9x*Z%(S zt0lHo{@{q%pM0VmDYB~a`hKg;GaifvP?-+QLAwi7qdL#a>E8?R2mN|+A>P85(o5O9 zdtV;*z^C)3n(n6kh1dfNf7O7-unZ>q>TJ#=3c(g@q%JsJLYozH}U+uH6Qg`N)x{TSHe+@0p+LJUfsQRctDZXgBimL3qAvU z;n|LBFCaSsH$Axt@&N~ZxDcY>ob4Gvg}Em^NrV?v?icitNZ~Z!7VkrcU>$E`3RHj9 z+jc>p{nf?a&)Y~pqw9KsZK{tRrnYPq$ zus^Nj2@d?t1W#nCIkd*QQd~k47X|ErF&$**6c`Ll&kWjncXfgjA$(57iw^&k6mAHz zk40c2bHneVG=a^{1-%#o#ZxWcC9G*{TrQJ=yn%h+>Dm56Dj>P2v~f}Ynn_hhWxyuu zUYO3k0_Tx*UXK*vEvdc*toIrm&|$P1*g__B?2+*!bwPf#jjexQH)hAK>Cc{ofKQ$sd@k14YI1) zsWfFVv_eYoBm8DWyM1Zx=e64B@KiUdHzLLcmbaBWRuC*)kGc|ssaek(VEH-S(hYTBZmZKo+ zgaNQ{YyfLk$(8Obn!$} z{IIHG-Ctg2QJ-Iao8G|C=NoSDWqV0KwWC5j;o$iqT1F1sw_;VO5_1&|`WuYSEbSCi z>NbleL{dZ=>ISx@xvhFhenIoEla@I3d-)sL96BTDE~dkpFu?5ju&h$UFwJfz*-fZ+is}*K>|}@5>rNr=i(;ha2tsxf1c%gxW`|9MAG+)WU7iD@FjPP`~N3 zc?ombGd`Sr+?A#xm})3u4T9qm%~3E;1=khh^_+iu0g$2?@IhpxTi~YmBju-_?%;&+ zZBTMKVHQ`fj^lg?h?d(Q9@5Z>utb%`$(P<%NYR*yH$YYc9Pi7fp9fwpF0i`N};CnbWyedu?F&`3IYrcIUDIV_>Ja%BDXxS*(w26+1;x zIC*dA-RqEv5VGq-mE4UOL->j&i5=~lNDNr?Y(pUGeD2@BCnX_qH#*0;?LWUXi~Fl2 zL4yfCZ=;?O%3o)oAAJ=ZomReKGYXXXj|Fq&0FFNk5;gL~AXBz#_V2sEI_*!B4Nh#y zyAHn(7#N0+9gD~IcX6gmG$l#pcW^{Eqy=soh3+MPOC+12_HzP!B5 zC?Fu9j^lC}a|8lY9bsGNag zm%tE}IW}ib9PJAHIK3 z&$?TD9s9}A1-f|gqPBDK2BsfaYT!VmU@gK6($-rnN9y}FKbN*?y4#mqOnvmBE;yZ~ zpZn_7t25C#KCQ`vGnDx5xKk#Z^!#}VwOy>f|pS=c?Vd*(7$t&-HLl4jWjn9t{+!~L^FgV+H1X!i0faLB+ z?;*NO{`~p#j>gC}G@0i8__aUl-ufDrNCc)oksB|?19ci-T}YIn`i#jsqIcrKX`!33 zfNR&hnCy){R5>C)-e|l#0u=9{59Ga?Soy5d!a|KP>mVwO{10Vlg1nF;Ud{7uVyzbS z@9s`F-oy3yA=-*X?2kS>2SV?8y z=qFLUE0b8w^|tPqS;VB|bkehD{3jmYq=cIGl0V3JIcK~pVa=r&?`MOapm|Rn128cO z94Z%s(OIDzo?d)?l|%I^cI@i4Yu9)lPu0pDC(`K2%@OJ%V@n(r)U=K_gYaXRiNBqb zELf!Gzl2AJI_G?(mEv~j!ndZL%9JPTpp63^$MHHEic4It{XvQiRgvd{C>taq^Jr;p zcZ?KD)}I{RS|ggcfHI>`-t#;V1_zf@POl@bya&U>dlxQ5g&ONeTY&QzqUtr9JCeb|A1jlvR7?FiaLC%(jt>t9vg_*WxA+V{)PGe8`lCf)rlmu%>nBHriCB%ymChSQx2=Q;_eFB1 zGK6M~!Mhx5O$L5EliLd1>{wRpvAd1ZL7+I8ycwu(whnR-j8cSBF5l7e$dmsuLlb2R z?)H5A_fI!7d#e>ktMEb;^)dD&wHxYoaRx0g{*~(u<9Qu6c|J8F&wu&*)TLqI%5Xbk znAgYS!y%zSX>0nib!GQP$`|32$Atcq;Ve;NWkW?> zMxXY%*E5NDkEy0+a2PWJb0BRRixAEMNiMy_17S(T$ww*t%42NTHDNm+4 zVdRd>z8%m$8r;O?9j2TXBH+y2?D0tcM^NMC$ueH?BHj$2D#Gf-U_E^eY%l4 z=WvyKIseR$qO?*qgYXw%QN3vS0%)z?-fB6mccL~3ObNU(KLxiGpPoy<$x zno3hUa(pgDLrd4z@|pvdKTPuMWj>XuClTUv=AI%EZ_j`Ukm!=XW77`TauqDTfp(YL z|LAY|<#p#HXQCw^NH=Q|7j^GoW;7nSHu_G=Pa0>RP_wgtYYDT2)nLr&dx(+TN&$~m z2694l$R`zkh$LPB(&jbKwv&jEOCl_{Gl^aA2FgG3cOd(~arlN0pZJ>n8=r&2hn4UVsk793wh56oK&(bzIMMhscni&AH` zg_w+gnXw|0%jOm&k4pq_a4&%W-{ng^seVWK125+zMexBei+tQ1Np;DMEblO0x8tcRD!MKF~6=n0lAV^c0`+@3_BQ0v;G*FXr`?z9i4( z2g)dpv?aU4ZbMdb9zu^_#-7O;rO&A#<5Ed50gZ}d(x(ZaGH{^rS#`w&_rY^L2$8{Bimxivm=eKo4IBqil&fucul zIJH)%GWsQ*5cV2>`ku$=%GAKXKIgKLEyAB|xxz}Ahc^>e|B$!9go+63aEQw{1dVy8 zUWfX(&@z(iF|il~bggtbCmfz)s+=3I_VHW1J5w=U&2R0ddn%pD84e|JIJdVON6Yrb>W0Yjcy%@p?Rt5<;?Fz!N@ z?Jh!j)SOiAtsz?F)92mX>1b2yyhS}=9!jPUPkU|V*=E_S4f9iVc5G0~g_jh3)p;dn z{({s|6Xw6PIa6u2%lQCBoxZ~~DV5Ey(zGW(sa55$^M!qf>|syLyhD`)iA~pMrg;&{ zp4a$Y#|(yWyBYsedFGw`gNLhGK~ zULK1RcgI5^LuD0}tm97riE)}#$&8GBH_Gy2CQe=h^yYJ(ZHgtQ-X%@$ukidIe7$!d z)qndxe(aJxvO-akJu)&IRv{7DE3=N7-LWeqBMmbYl|mHZ$j*+OB>UKM5{~WQ7{BXy z-}m=>-=E*__c?#P-&yDNIy^c1T{NMTJB%X^W9o~mG_#p17^>PwmiID?xi`bmkq)YoZOiUU+~#H zAgTp(6KcK%_(~+_d2yKI+nQ4ruh+BJ?^Bi2%X@|@hnJQA5zT%`=SbXGIE5CqbAgsa zqR%QO?libpDPFK^8X9^UO7n3#L|bN0-^V3?A1FTF(3Z*^Wlx1){Bsz#?B#Th!gJs3 zJUOdRh1UdncoEJ$h<81oM`qhLVoO~;G_|=`U+n$RN7>^pf#{QM`+($W7?u;WsAc8{|3zNyt@HLEH8PPTV$rccB^l@J*{I>|IV{yeYs z+%wznAvQE@x9{%U3L@~gj}*1U*9%2F+CI04u*x~?CA&l^(Utw1k4R(vNxgdtdl~(x ztazgDoo$S$MV~o#o*dgRZ%eLVREJ1W+?VRdkDRB+N4Aev4oKop&&;BW7fy}~S$(yl z*f|lfV~t+ysTahsv$M~oLuOdKG;S)YO0(s)xMlhMp11mzG)$BcTA7-$dY3O>MzU$n z#Yu}?HE~U*h(=v`mg>voVUeMC4Wuf+AydZ>J)17`$+|(xe_mQ>`$;p7gd5FP zCJ^c~ye>~0`o^brVMeIydfU$J zm^1RX#^1SDb?xWs+?Fa^Ng66XhWA zFt;5#GeKdgV21R>IG1MJVdv0ni37d1|9BC4iYciUWDuWU68;E(5^og0d%K3=R2M52 zaw_C0GNuwP#fDFWG^$Mf(j2p}EMHskp|(`0uA=4{93?+{q4;a`Z*;e&S8hw)+X{X3 z?Vf<~zV0kC>^-<@^P=wyz6LLpcUAjmQqZbt7z|QDJYd*Vy2bsxxJS@;!H{G8hu7D# zZfDh>n%AD{BM-39BRpa)@M>SZTv?t-2+XRi9Jcg+xazelT9bFLVnQN3m@9x;U@Xt4 zBCN&Tvae?jZ8&?{+qOsdk6L_nPuR~YDS!F?#rJ4R)Jk~!DtLJaw2e#1z9HV0)Pgu^ z)91;_lf=;C@}Ybv=8j@gj6VoNwR>)xHIhgGcU(>h)I2`@i%Fi|2_@l?w21 zS=nLo@Li|2y17e%b@juwtzm=<35>74i=vOvR1gApPNOF+d%SZ$*|sbDixru1qHq1I zn#v003zG<_Qjq9o^dXF;p>|MfwmpBiux#^$@mj`^Q<%Cr99BGtK;ZlN?1)3B5cPgU z*d1ZaDQLo%FKcvv`?0Fl!IWoh;XsZ3c|}9$h3RbFZNF?G{q)Z@=RYCDa`r>A2rco# zeUq`FstK05N37Xjze4J}+bVe>eZ~@WeWQJfn%UQsq!z8b=H4$hoLWqoQ7b1ydbMw3 zB6ulO7PIcAj_OMJPyV%bZ;qcVdb4AFJncug#2^G0{7cvKhm;F)#`-Atwmj2 zC)Fj>6|1x=M*N)0V{PyDRp#Aei=LOS?8QASA=~$ouq=0@u^tTW1_j%DaNb7u4FNXS zDY<=nin!;7T)>4_a5)r~ZR~*wwkM#DX#Jr#EgYI+mYzI=?89JV|1lJ6<&^{$5_=7`1Ji4mzIspllQhq53`19k!n}>1)VKfqZ|4-rE^;QC0Bv zDtkS|MSDHS-)R0IxOY+_spb@i=ZPnOkh3Cc<;piEp5W5z_Z#BVQRpOBFXV#&ZRN&c zW*37x#;vHc&A}gU4@2%o{uR2>e5s=nR_GK+MMEq?pqz!RJK#iDsrcktj43!BzJXpz z({QaH;-?z5rLE4eK>Pp3p0n#nqn8a6W!1Wmq`-rxdO~^8_KFw_ls)#79%Z!qpcnl> z!^Yjvw$!E|ZLtBgk8$La9LRxk#*OAK#w#CvXzoZeG*-cZ?EE;y5&ovYZgA8$o*QSd zj;|CqGRPEMkW8$v%;al~kK&Ih5ZSeUx%&0jNv!MEhkGgkR(;(jzll#Phh@DjZYH*N{fxVIV_%e9h@)egXy}q;gM@eSRC??_4hB=%74lkg{+;Y=h`7k zoxE2R07qq|CCe{YYCJI^$LOjV18w1Oh>}gy(^oh6)EYb{36iXmJ{$MA!6T+Yc=wPp zaZynsx@?2Vq0{fZasDbzqOZ~jAnk)KsjCnsl>l&Un5c2JQ80+b1AYda3t>J(PJ&0s zYP7XSn9B^tj$n^`4BE1MTk}Ohc~r(FgcyFh{r>0Rt(8_UK_`LxNn4IzO9O7s1_(t) z)N##aj;*DgbKfoFI>qPl=%m-f+b$FD)hFNnO&-?@?O*rVQ}27~$tm-r`!>P^1Nfz8^T+nmCGRJ(CR7-Pm zaYL+#fxbkn!wo#foEQPpZZuhmoAvU-PnFmzVQU z+0^xxJ{8O#Pb@BX9bqPRD<_IlIZvx?yjrq3nPNH7cg=KC>jiPRr%HBXmw2wWO38)2 zj{lD+|9ZIRM6DVx`ld^jdwmiP;NW{+rZ+>uz&_V*Su+*AVHWd*upWLe9HT zgL|_EfCY=wH9lfIH~o0{-to4+-d_68qcfD@aJ;s{P_(4t3d=Yh5*9+z5AZU&W@zor z$Cc{?^6zCE-?y23+@?{C1=>*IW~W!I+QR!cYoqzMZLKCz$E=a|3R4@0y8_rHW|a^M zlSFo_i)hvgy|n`Lg{DLA-XcZMcs18{wSHLLW%uIs*j{I_lKwas!Rq54@Hh^dkIvv8vR*)WFwMVbi?88K5+d7 zWqq8OI2V@-4xx2IO2i9!g`^2(-ieAG2`lB0e5*L8nqw_@#2l4!}db zYohlU5NaH6I{a41rDCp1#@)EP9^YtH`_Usc|ixCM;?wM=1w)rAzX-v;l;~a_+JzkgRsL)M&_LHBeE21{Xu{a8o z!98}5p&etptN1CC4V9qL2UX)sg5$>Hk17*`&9M)XYBvJ-l20rwb82?mnI?+iX?3Ji zX{#Y?0%g74CVEuP%tVFD<-cL3$~0C1qmy?mnflC-;4q5oQ+vcl^5?R~NrO!y8(g;) z&Af?vfr&rmtScmyy&n!GG9r}!Mgsc^9pQ6Yc#sD$=ih7{j1y?v^WSxxc2U5)Gn zB^CwMKvoHJD@{Vu)Yan2w;s0lMbYa=v4__DvD~A|UY3{FI~#1iy3_jwNzCy2+I;rn zpf;)`GOl;yR((UhXDv)^;@im8DQ<`Fw}-5`h%IOIP8Ya7vI@}EgFfMwV%5P2BkkQ z7q;3>Jr*0MbtP+l4gLDH)^hg~Y@sW&&;`&odYJ-SwzkRCbTL+`k2A^h8ceL+{@7L)vO$X&PJAuFZ(3-w*;*@ z;=>6WYRT#KR;+fIn8*D(obdj6zS+}R!3NV6Tv9==& z1EBf*a1=dH?bXqtSU}g4r05vO0>(lzywuqc^T6URLaPg5qxYu8vmn>_2LRQOtH&|B zEJWY9M2lFLE(sAcFM0g|bjk#K&Mev&@;7Xwx3BR}p%c&b4RL@J!zjo)uDSn}+OJmh zd@e+Iyago+kN5sk;$T0s1Ai%G3}ySl8NvXk8VbC+YCEUw>$H~wnty!9Ij#0g3Q(KhQg)qzRbzF>2!jEH;Vqe6PsqwAL$)F&F;Mx z&N%4|qFS$9X&)ZY(`saYQT!Iow~h`P?oY`ADLN+_#3f6kgD zFcoAo@aq>FA^DPnIh$s89|@7lWYo)2*^+YTl;3_YdeZ21LX$pon5>7=S@c)?;%4?A zuG~M*Ch1C4I$A(NBCSGVus?o6>|>WjMwg>Z0cW#9D7fWQK9u$IIH)CEc~hGb24d8V z@aFm;*thf(9r`D}aduwL>(O5Ws)86VeL>{;UvoB+&O^gdW2?4KJmWiG72wP>z>YQH z)xNyFeNXh=hWwXBQ&kW8Tj@F;`5PwnCYeZ8kWIv{TuGCD+ip=cU{S@S@VzC{(lgSs zv#tjEQx5MIVQA?ALZ^1m5e@QaG5Maw^iY;gI;g0i0F_s^K|79wbR_SWP7pYNaw;9v zj^K~7-}Kx+$4Om4a^wsd@I3>MipC^Hh|?+Od6 zO?t~UV>=*Iivi+gTzDL8pcxA}`i?DWbF)@mIX34y1V8ivUywnfz6PwwvG+R!_ajA9 zM7>eiM4g3%JmFV*q_ zfdxFLj?dGTCHfy$vEu;o1X>Vspi8hsAFTs2tZ(Q&pr5fhf5u5HqhepwFzsBT-p3{` z>dRR^!_mP!0}zJ@0{j|j?vK#*xQL2~Y$&t@lPTT?@ujVZ@5fA#IIJ&d2uOpFAKTl% z45W~1=GE+}pwZztX`c^e{LpekI>erUred!ZttT{B3w=XEz*3S>gG5|Vu^BQ$1!Jik zh-Bt+2O~VshcW;t6AoaTF2KH{~D!@&MVY|u4=k729UR*a~!$PBv{a95}>5@{I| z)3%8t?v;ZFES7Y_ZK;io-u@Q>2H53?ZB1}0QagVc-*aLiR8U}TJGV}shPE;K~9^TflLHji`;?7BfxP)&P%oW~=7x<&HqvWs8J z)Yo&YITWZlJRXVbFsFF|Kk%%iq~tQf@U=8|(x;mrMXg&1yR8wIa)gz9K$J9hSAHpE zUuJx`CDjSI(2_`vbFrT>tD9_;7fQgLRYG%)PU_PhC!DcXLAU<;iV!uEhT$Q_cAURHhzo05Yvi%@b7Kk}6tQroO7OXaEJ& zB`)ZvMHRgwXQ1-Z4Lc;P-df-f=ie?2SEL|Z$S5x?ooAxFlBF3N^DkcYS7gCtrFK3Z zE!g1DEpA7x`+{tgJ*~eEL5UhFZVP0OvVR~wBQ}2Ke0yn(p3XfFljm37gnGoC;|U~J z9I>!GV)byC;jeh#P#F};5@0_#Xpa@Nc?_30>AJ?5yTqC2HJQKOeD>_wXKshSpe({4 z9hI@yb^5!A)C#((XC1r0E}gokz?zl{x3#0PF4v%y_fd?F6X}NwZCBSoas_g6XpKM9(V>P_g`Xw;TMLl#SjVy%9z~rx_)b$3ljrz~ zvCt#oiR7)E4kYn*9_&h$(n97I7TcL`bW`vmZttM#TynDI#p5NZZN#rhk%|xlAoIA5 zS466%qOR7$GRfZ_22^Taph%Z;NZRZ)fS>0xyO>duDn!3MPgM)q)(Z!Fgv%sJRFI(_ zw)c8vWhGXM4$RYcd-&8}Ni*Jj&)dRv;>|+X$W*9i%X?TM@12~SXztq&K*N-ssLaQl zq)2BWMYky-;k}n2JtI7xmr3)aecA**je7GBA3pd|#d`nZa`*-(O4wc;KYQ<@ul*M{ z`z#y9%%usD{&zW}Rtw8G5}H<>bmtSpUoDQf&1V+Wtxcq8=XfS93n%GxcGYN)@dSuX zH9*N)+3#mdlD=7gG6&3#$L$!rP*5a%K_pHa=x~}qx+(_MwI7TVeHSpIuCH^OIH$;> z1G5xJRqO)O$;pJv8&h-{ zsf|xovQG>hNne;qme|bO8u*D+I+YiRwZ-CRHCtlHw6lC3p3K70`)j&!zf`CNp7uEu zA0EyQF3!-yzP`NDl02JNpXnV7m^1AnhPaU$@U~QGvxbk_;Y?`G&C9m*u<}L2ed44U z6TRHv!9mT>jAO^h>I%NhCajz(PR7h1|CGBsz>87%p2ph3OG^BRlAXe3M-qzwft^7;lAm8w zvs(gCt^$|C43~q_qe#E`p|UwS2ffSq)!#rKZG3%jrsRJ#-xruP?qv!JbO`-Mr==sxI=HV%0pje2RBk`5zv z+IPl?i}xChV;x^;aD5WrI0Y{kDRVFqmbja*b%R||AW(Kqwj+yjx0Wx6ZPso zxY)qEaL=1YK+xsCBv49MCDvl%c>V|dd+rIQ=7bzlOU7P}VkUT8>SbenBlAXiLcjiu zeu{bx@+6U!D-j9~uV(id@DCzIo^R`8m*e~(izBRXwRVxXo$a_Y9Il%Mr#2n+1gcOf zz2SHtm)~6*F zS?o$4Jf(NOHS4%na>j3ZK572Cqe{uY)mb#K`?PWTl6#%>^1()|O+2m79;lfnT&ln1 z8~-g4HKLUjfX4>KO27TZSqk0j^Wxw9vMn#3RuOx4T2mx#_n`9dgiq1(W#Mx??{K<7kzeFDWEsHF&7 zN>(h@V@zEkvP*vBJ6K#;M`l3Vp;j~BFQ+hBI9|68R0?I|^s6{bHsy=Mj>?dEkeenh0usQp5p=zothY@glh3C&d3C_kKQh17gH%-9NIa$Np+3h zc;ZO~hqvJSG4j39sp<}I!9pG{q%YA&-b-nTWhomtMb3&B>u0gP^LgRQ5yP`6<%+rR z%k>7~#8tk!qrw!)ldr|qeN?hCQo}ZJVf)8}h0G@DRJF3; z2(mW-f$EHQp`f)?dfHxSi}sqZ2Xi|ao@RkYwMzjcOk;Nli(0RB{!3^m7)oWVv2otd zZ~Yfot*ap@p0;LNj>=?FO9Y6w? z{@&hQHU-|vMY#0gT>jd&9|t1HilV8)99NgDC$(elrlVrTc zn81`FXB0cSF86d3@1v#;?k>*J!!N40624tr*v)9xe~ve&+mJinHloh@u2Z=@KfATz zw-$fLi0z<3wt5a~5<7eJrBA^y6$RBu^jMFoH{O@Vx>&sK=Ps9T^WM2HEV!%6Ub&4j zU+-GWU3p`43%TG_4<>PSvfnMKEj1fM1+}I60B&G8OXVvwU^m>$RVUaaY=;R zS>?u!U-EToM!S#V6d;muh|VoRnc)nrIC|3blDwSd-j#@(T&{;3{H@C7DgNwL*wILk zWkqLv$+Xo+dEou&moBPfh)swPe;cdxSW8cAsh3$8b^6w4(E!VIf*J1J>UoY*f$_`Z zyqDrGM7_jJ1EuLF^jU5x z%hS2!-^JYOzc;85Ff4B;S~2oG?q%px++i{MxFDa9kX`?9;n+RE<&`HHfC`SnEe*N! zd3@{KyD#T_s)!tE%O`h&!?;gHZC>uDfIwBjfAlUBH=Gm9pOJaCwfi{IU=4x{=#yXK zpH?lLBj2&Kk}fhw=l9w+nT%z{@lfHByWV*b8yB+D1L__Va{~6h|22=O%DYSzXMfRX z1#9X0Aw&RjrW6CObBX>Zx4HDs`XKFV#x^!KfK)>0AK~2Gt$@yVBTM(h(*ze8CrUZH zD4fX`>i0R(yJa;U$#qU$+Z#!L4IHZ>-ZjhO#tr}ToKf!3fz`>qmC+z|Dh{aQV+7RE zDv{f=$gHjJK52rUjZ5tfL)@EF>Nz1lspxQRWC0*K@fc-I$pl!MNC=m&(Y2Rm~}MIFA}UaEJ9#8SfXY!wlC94&DuQhGi5 zieojXqzLVw%+X)XUPY{Sdc94@gA~{yCaW?@4pO!nLqS|6ZeB z!8l~5p_N(Yp8{;-YW3l&x&q&GZe7XcT)hUE1~29)nzmt)Z#VhMlMU)P@jgR^LoWL^ zU%4RV|k0y+vmo!u)CfUZ{5`CcKjZzsnhOU=T8h{P++0^==K;la6Js6 z6p*(mG?Mq|-Z0JfTk=+^6Y*6^I6$<0Z*WHK-gsW{?j2gzlulphF@#BE#x1!f$HcvG z>PW5$AI%le6jk`X`RM}BxBK>VN3&`v&9 zADl0(xW9~cU|~*LUif&z@tGI!www(mOsTjV$2yu9$$a1I*9eeHT9tM$KPUPPwt zowG}RUp&2gZ&7&-GG#fO+=(bUGwn$cIxw}V;B~#ifa^`5(Ea696@JgS22P)I*2J8e z6KQ%3@bqgq>ZJ!Pq%%hBawi4px{N$-3+$}&|DM}26jpOiaWPLi8qiXipZ*A!iSo?t zLmu=?K4`gyF^BQ5w&Ue@ztB{uZF_8w4Odd2r33aQ#yRQ*#vQHH))hV2s*1~65enm4 z;rqX)mQPi-@^c?=d4moq+aGBClueF zsXRW{{m8|C;tpspm7dYCgOmUZZW2J4Ywz)K=I(e$(YUjuXIaY*N%&nAx-N$cae8(M zB81L-W~%U9%^v~QQBgHuzl!4X9HREe)F%vMZg%nd>ZaT!MMA;7V_`wzI8V;6t2knX zeL1GWk+F+cw#8({%GNf_j!lz>LOnqlM68cEBqcoE-D_w8S-;p@?u1Q6Eu>dc*Qx~S z`m8ti7llmDAiK_d6RCuNwC7p4-v2+YNplOO;i%Br@F8V1oTr(c>j(NuR-;sKl{EFZ zB>ZSA%s-^X(=V%o=Bn=6D=B6!gQFox_rZ;$9)H46t$48=Z4)oBJV|L2xgW)}X{>vU zFV-vn5iuS-Z`16Rv=>(}4FS_&TtN6hq)Jxxxi~mJ&H)P|q4&nfp!w*_Cg=7ir`#c; zQ-S<-&#m1%&%Es|b$z26jOljiETz+Gx)7n}?^69u0VrDCUZ3-P0jJO(MU*jZ?}kVY zo_bdFnGykq!7E?NpOFVXnK1B7K78Bi6b1z2N{hfgrGIzkzbf=pHbNA@-;aHJ!FX(C z>j+pya;-1g#-9Jz={in$iZ_whxoB3i)$o41kH?t@q~(|Y{{0z^MZ+_E`-m*Etg(2X z>{p^ep@$Uwi0jO;x-FI7&s8WYERb+y64QNSB>9^U#l7hXcm_>%UUhXa+0w%1MK)$G zF1~VKbmQlw-q1vNSZ6{c2%nUpdQUhTx*dBT?s${awB1K3)8?LN$LP~${no(5vJGGA zt691q$~b@z<^@>athsEb#__T=c$3HAv_NSr~f z@D-nJ32s`gPU$5PJbxVmh%cvlO)ulWd!4iy3nsa-*_+pte*N=B0YZZ!F)WzwBbwY^ zpiw?)v@ipBsC&D4X_O2VefW39#rt_X6ozcP)boMh!K&uN>B+Usb7&ggsyKZ`=(MKc z=WOY>)vZ(Q!m%5Va^B^-;96355x-j7HaJ3b&jDlPASE@n#UIQR3 zeK^1b92?z^T6q7C^B4z66VUAb3+-73;HkTU`)1I99}R~w2?3U?us7T_1vG9DSzOci zWIqsvA}Wo84Pekc-tY3|_Ep&8ZXw-^ZL0^}-ieb3z3Gz3?Y4NuM0Bdc)L;(~mFJj$*MYtmt2ytM3+x#R%fW zURrhW=g(`A5upD_`Xrpfh9NqB+@Qv2aBz2Kd}!vzsMzcK;>1m}31(Pnl0qQPZ%QPn z>r~4Tcx-M2W(DwFBXfhPfE$l5F>FkJ6PSwl-%MXAf*1i+_ra1=FXY{al){f6I{R15$Pvt67P-O}z<;QMJMnb|=|lnN zeWmecl7;8tqbDwDOeD`?MAc-%C5`Pqki|uksumK`l` zN|w__v+6+aAg~OZAYh;DhclwOvBSX5IKELZXD)zSE%MDV3ZW}fi$ijW_iF6dO}nh) zrRDL8+C4s^&NZ!Oi`xu2J#;M;e~!myS%yw~HrS|^Iydfmw<@9M;9DW^K1MA4) zd4+4r$oW?1pI_KFv41pxw!PHg!H>M%OCqs(-Q0L zmhd*SPwvCz+To|er4F37In(AsBn5Ig+TpP(E3-AD9?uoaN0m2iJ_^++DH7VrquF_Q zUL;{1%f;-+n<}4@)!N^a#mhI;43bC7yB(O9V%grqFJJ=4sAFW$XLn{U)|t>L-1)IoBvA!e=g9dA@r-38n%ZgS750`IhQr*uvs~(VFcd><+@V2BUz=-x|FiYl2m! zbL<%`pvQVbs+d)0IGE3?Ipl~BGbe1&r!WZ#E-ODp zDgz401!MRjO~cob_5w2Yw@6DFiLg#s353s zqlmZOG4a`f66rfr7qwU`8YM#S%=v)AmfU&o+7J&qsO6NwSq{a*ktbL4Y_%5+lX7|i zQ>7m`dSDv;2^pDjTm^M^@V6bl6QX~7mMiH#ji>Jt`y!sEW%29~heMQKGY{*|wF;O;9TwoFo;+wibgHt<9 z>D_9m=ln}$U3omfyY-VYkbg9aqOmzgfrr*fI^6gXtZ+w{*zygI3$Q*r_%#4IUCoep z=M86ZNtz_#P28H@N)DZK2X1Q6ex&H?>h_aKwj2=5qzCE;co!M4><7R%P?kpzs^tNu z!}eQk8O|q~@TZsyXz{}03aA>!oB)UKdz`D0(mRCjrrEoQ7FSAZ5?kZ=3|ecQq;%8* zMop?+FNkj5Ldw*^?9hf1^zdI*d zSKe{*0o_eoz3&_3GqLTyp_0t7eWC@pm)CS&kcl1R;p!uux7nrdb3FPx?ot}pzBkA@ z(@kLeU0Nf&-BUe|C&c-?d8hGXD;K3$UBU;c4#PYkc#XcC4tnP1`7qh;9P+UIyII5ttMhWs|@$^+LfN4R{GQzq;}IgCQy~@i?EoXf-WO*_Ai#^OuKpE<9eogJ4#TRnSnq zV$u}(gIrvnLcob-=&Zi;M(C)zOv70)TcBiCiJ@x2>G<9Gvd}8$bLQqmmPaPs4iM`L z$5-1UkhRDA4)jN7nJaZnDnMmZMardPqbyRvMdZJb8FGm8)C%b|7KrW68k08Z!Z2h$ zlcH*&?f8J$Q~SE2zMyvipgw+0RQdytSS zmI>`{GEkDb5yeUlQ+~L8-rEulOU3hpuUe{3`pv$vQM<#e{}zn@eWY&^dU~0ZNl6W3 zGf)DMo&d=<$)pHh!)vi2kvEv)(p~(X^$R~T8x}fq^;O`}`tIC!`&Fogb5nQsfk*v& z470oi)|1iVqUz;W6phal7xpWG$$NS)S+SYc7 z2l@Vz!c`4N1DUiP8wgwcbH3bNawTM6tfRyqBytCHVAv?tq{IyHX#A5A`?Ap!A_N5I zJoO)cMD|V#J+8RZ=X3p8W!!#WO!f7MF-DtL!@=X#%0+eK8B+_QY_wbsofjQCzb=<+ zYOrET3SB*dB}h|*=wIi{#z`N61Qn5kq@#&%K71bnU^r?$yKW|p=e1m1W6G4qYu21F z`A`C5dgi0y*!{=_3OU=b2G(mK!zK!9YgcV|37l;z@8&m}_x%P9wReBTXD6k_ghI3b z1w8%N>*p9DeVY7cT#$(g2nO+_2*vYoCPgV8y9&}t1=8O&KJGRgdX_ISoAsYLCnBNc z?iDbw*_br4A*!|G-@^`~8rmUTmGtVC3dG1hr)>4U2&ZyS8brSzfAct_d+wRn&DQ7v z*%?8^Rhnt0`mr;y{I&BbVT8ENnZuQ)St}s?aMluLQ!>%mIL-2~m9&az;9#fxm9X1< zu+}X9y|AudcJ=gRq%b45gGqlZzI@!uexs~IqrN+)Dh^qcUaH|o$U-EK_ln%XSQUhH zBuwX2P-Z^WYUPw=t!IH0>4Sky(?K>=ch$5FjvOos#h|xOU|h}Q!!~E;!Ffdn)66_! zleUC!))|y$^!401^OP|=cZg9_V=pjnXVoUW(3dpiAu5@LG*&@4<2$}9B*EjK-xN3~ z|NOqONH6_(#7mzaoAX$&uO{R*!IW>J>sv<>PmQh^RNqH|K+IL=p2M?ucJ46I;B+`? zCXWEEQTNp>I1`I}o$z|FNF3A?TI~ms$nFIfft+Q-^)_o>Tp`IwkMMZw=;6Lx_}os%V)@`WhZv)gH9$bOg%loX@oiCSrzLM%3QAIpvO z`PT=fAD)x0(hBp%$#$$vIuOCuCj8PZ&kFvEAWQ@N{7lF>+fQX1I|5cwc+O|$7{x*R zK$RJ-g*h*(9aj1!P5rQ;D+|eD1QMKrB^AlJFL+3zJ&{8J4@YN=RxY55bH3Ju=7aGS zuio)-I>%L#)zcwFt@=M02t6eTu^g_bsv^20;1e^4*^Yt(&)Dum`2w||E&ynM6|WkG zjHS>%sPLrIUdY@$d6o;ZW{<(J!u-F@r0CfQu?~x8Jh?8Ud{SZIX8DJTH1&I+D*d$B zg5B*RAj0!&Rb%Yf3VoG6eks=beb&ApL{pEz3n2aBm|oJOqHs_V}AB= zzW@8%L!6DaS6B?&Me;k!#XFRG;C%4j_}g5afd<2MlXoJEK@t}#ZC?h9|2cNne^t_b z7TfK+6MzWzy&vru|9ea|EzNbZ(6R!53Q8e+A)JBo5Gw4jYI2UV_fDS+kemxAnwu6` z1-Z&+>>ffuE(6SF5X?C|(LgMunj=S)Mi;QRnc^f-9!S2Mq=jU7Sh&2SoW|=|t7GY) z5MmW^_B6}kJt_UOn&NS)b(elwQI7ZC>wW)cWYscZ&o}GeKl8WU2ULISlaD0j$l6ws_@3MNf)5|8$q_)T*8yBSQZwxEX~0s$ zF*#qY8w>#zqYID)WS08fnIl%`(cG7?h0>54kS6g+`t7}t>Ab|=0@D;o9l{opvL6*- zwV%$L4EmUurB<|ic~}x)L=)x34~45X8U8=B_v9DFF^g%vmA{`0hC;^vnnKgt7Wn3{ zHAv#=DifQlsa;f0(i5ta=L%u#sAGq)_X}%eFRe8suJwLscU&oYf0ysU08MPL^rLsF zY`^VDa(^-AAkKOkT}!3J zcz5E|yVy$T)9n}%d}n(%-}ZFOtA zl|Ko)1PC8S6hgwFp}okL=Qv1#eT@s+rx@2qq$j9t|6xy&O7$8rU!r#c1bh96tB9p@{eJ2S zL-Mef7;C%$pTyKd10B*vzs3mL2^^WO%exKyPzsh~rh5NQViU_uYn5H9pN@?GfmtxPj*^>2+791 zYE2E}xLD^EEIe?Q2pnv7wAY2%WLi=mG@7y)GKqD>ayuN&zkPS}w!k+9lT7IlyYP2Z72VaNz8O&nz#t4ZOO`W(R!>B=2Z%*4CMs^t3xZseqwLmnXl; zKEDrKfK>P?M6zOn;6eT7a&RU$gT!E%LkCq>1ygVygZp#qAn1|SH0%9IxU#@f7tAOf zOliR0%D#_y_%We=;Pn7t)?awHvtjD z7b>jmshK<+wT8!$TGzM@n-#Lh0^PP=N0YuRAmUx^mT~1G6i+!A!|H%|`Rdg~<;&G$ zad&4d;)31yJgR@tB5YqY>1aGH0@T3*^vw@&LFWB_wxj_;yzfZ;%USNsxgeXoxi65C zOq!%<1&sx?lU&Oxp6_8}<83`Nu!u5=A?Ju&`PkjZ%KqY-@Y&YY>Noj!4+ei7?7eMGP&XFl{wvz=>P5DkY73Y`tD8y=chDG! zL{+Ns_y3F&62)jxIqtm?&qS2R&u~&@rNVSH((74cyR6~F){JYc)3f!1e>r>i;8qsm z!ZgE~)!fZO%j~wwJqcY#mwg@wI`31qv%X)S58F^I^}xN;e~bOVcKuEq%X3Kb`VDJ0 z=E^a69RIl++|;N4JtC`d+-caD1soQ+Fufp!jD%fgKG^x}o@4YG{0x=JPMN0#P#_K_ z95lZr?x>_iqKkQQnP`%Z3Tf^c1ktbPojP%FX7~z!w|o5Gg$SMwQWMi9)grbQlFdjb znDeB$U6!hd)N+?PvIlQ*a2`86ZeqAIbp2#v!N|t&;o00bZ>p-RRj8`>?BLly-jZr+ zBIgu0yMJ3=K!n6h{pTs*!o+kMd0Fo4CQ@ZwNcbWXLRIqpxr-4`1#!>rq>d z(yfS_qxp-yez8LDyrV!mWzig-qE*|{TP~Ss;_W4zSCo2gox4MXzD$x2;xCQ&le)k${B3TAcMP2;F0?NuSCuX*_NG-_HwnhY~n{GbdTMW}ZbB zj=((4K!Fl-4SD5+MiZADa#n_DWerDAAxIunQJ_2VSlplTe@b48`*kVGTfzy)nwdwt zG5oy@JAQg25Z>m2V2qo!6G-<3sP5K56A6Q1@suA)VP6^;owj5vy0!qSA0wPjvOV`5!&Whh5dzkog$7R*UZRBj$ z-hVgs@p<9`>46ZE*aO|Shzh3TOA+zn!G=M)kC1|Dg;`ay?hlcMRDOv0hIiq-Xy2p8 z(9GNTY`63HwtXerXyA5!VPPE1{`dv8EMaPinDwq6rp}xL+o4JCL3l9*JjTKbS{R0d z^A>vLSa>GkM)GXdt^HP0F{W-0lc33gfpT@omLD7SnoagVn7Vr^O^jkuFUIJPlh<>M z2QXdk%>Lxp6L`7{0hTqnV+?t%!wfLS8EmEB9c6AI77p&TaoLgcL!6}tXV(i8L=WO8 zccLM)H<;!UPk&ixW$`?WDH~F!AHR)=ULW3l9bu!YzA?{u`pek+i@d4H-xQvimUo84 zGt$70kz-^x>`zMEb83vD|3Lcf_olx2WVzKWY`Qjm4&i@%>e90plb87581S`@pLKY< ztU5}RX|kTCyqf!;!?HlmUf`lj)_ukX?zYFQI2-C8#cl=?le-5WoQ4J!3FH_U{QM`7 zY@RupM|x)FGX1~4l>4=#e6>D0_)xgqB!w{so23HM9%OcC=!((v#rY{Q2^&l%ZW(dHZ^WKo@gt2ZrcSAP9toSw`8P&q7>~gazDdO(zBZO5 zRuGvCkwI#v`j?^%E`xiwy|Opap={A^sx71CPAVa!)oy4X3jx0{=7NhfV#71{WV!(9 zhp+eM!SQ2JD8Io0S-xwp-Lp&}+Z@;VtNAv|s-fuhOXs6*} zrXxyD-nW5XmXMS*AS+f+jftU+k)AOz(dTj?MP_SI{J*QKOEt&V*F!J<3h{V+ z-liOdw`D_?f5#Ny6gDB|S0(%Z`H)&ttqPK?LS3-UU@bUhRdSxIu+Q1q6fDWJ45@N> z3BG@WMF<NMs$CT(Vc!)J+n0w##3=zi{K-a9~d2VI+m2J`?oztu!hZwWmyh|_$|gp z;ya61|99fyUjd822Oyv|M*i{x;Xe`#;)yY;uy>RS3ku$ShaA6os}LR(#PwgW#lJmj zv@3cucKz1hSXc}5<2({UqKhq(yHvo9Xc~;x{!Lz2jAi7DU@vE{q-quPH$k(z>(}Ld z$=`4h6}I6TA|1(laGgA_!6pRw-JMxK?AyZ{ z0cT9&B3>F9_@%L?tw9kqCTOFZE~#E58*2%WU%kVw-uEMr6p+!(EI5&T3R-fq7lQfL z#o@SftfXYwf#ils9MI!kD1K?c&sC{izh7FD0)Xntp%S{2w3tYRKwn=sb9hPamSYu* z5@z&wxa=ZWz`YO^WVGRf(lKM5+S6bN-%P}W2_YMX2nN3rPhV5Tamcs@f8+E-McwQ5 zEqp<%_UnJV*?gfR4Hr6L*{i$5t6yKnh2WcT5l`+XdFBp=8px;`G8X$G9`1fw*4W@@ z=!cuX)OIewB`064zZkel2j~oKO4Q10Q5<1BwG2(_d6eqs(s;o|cAPRU;L^<6 z_B;=)kQzf*lClVF9aujq_);1S-o#|9m;=2kCe>7LJ6dO8|Id{VIzE;hripcG@5Ak4 zGJLTT81Z(Rp50^na+F5C?!&&}lk_w0&=k2Pp!vDw_Ug#@r&F9L6y!vS${d!bB`Qxc@}mrbx6Rg1t1Ghv~7a6#?ra_2h1Gup%}Ff1?_L-as#tro>p+Q z>CU^WS5221E622SqHYk5Y11I)G6~ZRT4q;#33kHr!eAlXJC?Y>g|AW_aO+ z$4^OHpzVNu`7ph{rR7WCUedw)Eg&*fOsZ49SA z-W8ZPbUasMkLGYX`o?dRxnu9)Hg`zd-qxmx`9=e)fQheHT1tMi7db-8Up92^Lc}J_ zIg=@AR`^2Y%=DHGu|G?_A?!bWa3>z?Hu{uw*8#w9&Gq%PnC`wCD(nMu&k35hO73uDTMAXTKH3okhj?Z zr_~FN)Y)I1ISLt`DM+>lf|H(z3PJ%k*Ob!W3Fd~*@1263Qan)h7A-_y=$sS@pT&^! zA)r|)z1c+>&pzxH3V(aqXfb7h6In+wPh)a(gtjckX$1}lH zQxh#z_;P%GM#ZX2yP_bXOJcxQ!5dukkbYrNk(H5EK|*3J1Ss`~O!;V4y0?OvqIG!w z4~(ffNZpz-jPHk+FW|;1(p^tY?ts=}=HO5GL>yzv24?OHAjz*BC|lGVR;2Plm$KzH zlp^b1&^G53fct1xpyabUsaT%_I)42A&Ex#i)w`V72<02mh zC$n`z%ZY{*2bJLUoP|IF&i!NmjJUz{I9n|shDbq>ISPKS(Qt9zEOXR=fjmeGIWqS2 z+Z_sh+V9_0P)BflD`vQI!&0XNJE%-A-kF8#WynW{w_SX%%mMc|l21IY_Z<~|*fsMT zBu%gUimk=3%Mwv7UnA zY_nqtRW~A*fQRjqs@(4BQ6zeB=ajIgBqC79S53RTD*aS)n0Rz@+2{EbO@J2#dEI)m zn$Bdqw$E8gEk5ooyj;2$TT!E3tc75#cw@_S=luY;{lf>^Gv%T8?pw@8E|Hg?o)fu*kS}Md~!RFxbq+y-rp&d#N*JWYguhf(WJp&Sum&=8~|ONc{9L+ zs*73UrIXvKhHF6u9Ki?G>x&p+o6v#FveI^GL{GoUBpQn93`FxN@Qh6QbiD;#JY$#; zDxI2()GXfL^Xzjwqt~zH9p5`W={_W0CV7=H*C%2q4J-te&I6Hb%U0$*Rkw-_nbD;l zr@{jC9apn;*+mnBdOzOJ3F$LSCf(B(?Iz-oDx!|~BT#`cvkP5CT2hMnHd+Xyt;q60 zi|@wX`l4TAT6*@QYNE&xw7g&>BYg6%Hw zZPgwY*!2d0rvoV22~f6D@Oh@hq&g&tDKRCr&-S5k z9zwr1^XIb$hNP-=^#x71ngv?E9MIa`6FFX2kJa534co51ndWhr!$HloEWSUfbF*hGU0HM zt(f)4Tt2f>9f9H8;UTR9@#qnSej$WNV;EB!oT!+wAwdwd2*;CufkV!g$9q^@9Lmyj zOydUB5y?#2tNv zj>8ksD|-}=Ue39V<>5h07{fS+$0>4@-t2%vjgD_PGSd~kM?Dxy=5Ll;cc z3;tlUi!2khA&*DkeW5mwVoy?Gb)W_p&6H|KM&olhnN!0CngkmT*aP;CGSGNDNAw-~ zgF0ddBM4@dI};M!34BfcW3X)K)V4-=3C zboSMm_!H(+9WI8?n_VcngwjF-Xj{J49(D2c?ebPG1WSqspjKt9ks5Km_l8dj+3qnf zbBkSOT4BEl@+vVJhzF#_USbHOiG}3BofY^GE2&@`kWs!lu;rdV^;HUVJpO;YR-RSV z<#4%Y{Ul&l7aCs(T0IwK1Ie8kp*tXFexo;vtrQICL0`>Gk6?oG zPoGo+=`0mZ(mdr4+9)WWN@=}TiY@n%d|pM0ZGCVjf)uHTXyW;wuI@Ig2_(`qU!t9$ zVZprf+ocf3+nP_lGMTlCypFXQSpWAek3)O!gDdvz)kGDqAsr$)h8007n*nN3d9XDO zqiEu_`fty5(tVfL8FJN#qq}Xe2wol`no(K^wb-c%SyA$aVW`C3(hqq?F@{7|o#Fa- zmKFpx<&MF!V}1rq;`3$CV<63+mClr~Ro1OqeE!vv7Y2>|L9s&qrYa+5U|?*CR0yEs zI@crJ&LF#vDbK}^ZK}HC+E_#~Gj$ct7HL5_Q4pl_6~s59e$tW)k_u<+n7x69x5{-s zb4FDZg4D?MvwYYa7SjcAcS}UZw%u%vgwq`ozi;bWRa8kzza*KYag57(zANkB6 z-!!zk83mN4@w0@CXkF#Qxp!| z#2xYeFg$v8G%sl)nx^OY$s*pDG^y`f=ZHO!J5d@1!8a*4zuXdR5Ot!qL zXovXpj2GoGxcQv}*`N{1d}uwppl>jJVeT?8YVxYV-UL+ zckhp4N~XW#lw;ky&=|6YCJ=9iu~zDi&x4YP&Rh1G=Eom?j}u7F2c?86&3p4?}~ z=y9BF^PYrtNW1%$PXxL(^`fg(>HsKq^~V?syrATK7ml539I^{{xd5uWd!+!3ZUS>x z&T^m!#Un&7aK66`A zZPNGHrQBC4WL@iz5swU(N`>wikBlg8O*02-AE?!=p^`@uxjnB2|LNh4I4t>R)=H?_ zLr<((2(UXjl&;^2jkT2OAD!K7(jtTo_Bmy&1?Es{pKgkH>a;^wcJS!YLWT5B|Iq>< zNjv9H=>Y5-OX0r;DXX7IoK`g560yFs5WrEW*^)Sc)bjIP=uWvQBnW^Pa1sT1fiiGH z%{XcT$|kOfKL{z_G~CBj0D#Sy?U{r#_bwfxc;#$upb_P#;mv4lVxQ_ViyK-$AXLA6 zb*@MXeA}A&4L<=%UZ+iFutM%l!C@c!oIhy9*%n`WE}IcBn7J8T-xj1)OK;=)eh3k^+NShWyE`vE7fv&gl+TnKAV*Tn8${yZUIS*Y1}bCk0#kJhH$lZlwG&bg`{CD>{&NFw?J&GFa=hr* zEBC>*PG@4s9XuXy;Qigx(c*wp=E+^>>;!n+3dk$}h`7f66!%%Mp@y(o!A?O*h~h*6 z@tQIB%VC8$5A3?>3q<;;f+E^MqN%+pxzDEubyoZdfa|hCBpZ%)0dx*{6I?XR+- zFu_CEQOh~rB(sI0c&JAm^i5J(x|UjG^{y?f!xajB7NmC7H*~!1I=-1~hB0P*J1e4G zKh>V7uRvbnOlS?t#W$kQq(?T`(7aS*6yVx;4hU@Wwi2R(wt+Lpdn+t)Cjvvj_~1S_ zx5uwTuD-|kgTVXTym>edT;AW$wB-xGRkTyXJU5$qWD>0<&|jm3mo + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/extern/pybind11/docs/pybind11_vs_boost_python2.png b/extern/pybind11/docs/pybind11_vs_boost_python2.png new file mode 100644 index 0000000000000000000000000000000000000000..9f17272c50663957d6ae6d8e23fdd5a15757e71f GIT binary patch literal 41121 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 BtT6xp literal 0 HcmV?d00001 diff --git a/extern/pybind11/docs/pybind11_vs_boost_python2.svg b/extern/pybind11/docs/pybind11_vs_boost_python2.svg new file mode 100644 index 000000000..5ed6530ca --- /dev/null +++ b/extern/pybind11/docs/pybind11_vs_boost_python2.svg @@ -0,0 +1,427 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/extern/pybind11/docs/reference.rst b/extern/pybind11/docs/reference.rst new file mode 100644 index 000000000..e64a03519 --- /dev/null +++ b/extern/pybind11/docs/reference.rst @@ -0,0 +1,130 @@ +.. _reference: + +.. warning:: + + Please be advised that the reference documentation discussing pybind11 + internals is currently incomplete. Please refer to the previous sections + and the pybind11 header files for the nitty gritty details. + +Reference +######### + +.. _macros: + +Macros +====== + +.. doxygendefine:: PYBIND11_MODULE + +.. _core_types: + +Convenience classes for arbitrary Python types +============================================== + +Common member functions +----------------------- + +.. doxygenclass:: object_api + :members: + +Without reference counting +-------------------------- + +.. doxygenclass:: handle + :members: + +With reference counting +----------------------- + +.. doxygenclass:: object + :members: + +.. doxygenfunction:: reinterpret_borrow + +.. doxygenfunction:: reinterpret_steal + +Convenience classes for specific Python types +============================================= + +.. doxygenclass:: module_ + :members: + +.. doxygengroup:: pytypes + :members: + +Convenience functions converting to Python types +================================================ + +.. doxygenfunction:: make_tuple(Args&&...) + +.. doxygenfunction:: make_iterator(Iterator, Sentinel, Extra &&...) +.. doxygenfunction:: make_iterator(Type &, Extra&&...) + +.. doxygenfunction:: make_key_iterator(Iterator, Sentinel, Extra &&...) +.. doxygenfunction:: make_key_iterator(Type &, Extra&&...) + +.. doxygenfunction:: make_value_iterator(Iterator, Sentinel, Extra &&...) +.. doxygenfunction:: make_value_iterator(Type &, Extra&&...) + +.. _extras: + +Passing extra arguments to ``def`` or ``class_`` +================================================ + +.. doxygengroup:: annotations + :members: + +Embedding the interpreter +========================= + +.. doxygendefine:: PYBIND11_EMBEDDED_MODULE + +.. doxygenfunction:: initialize_interpreter + +.. doxygenfunction:: finalize_interpreter + +.. doxygenclass:: scoped_interpreter + +Redirecting C++ streams +======================= + +.. doxygenclass:: scoped_ostream_redirect + +.. doxygenclass:: scoped_estream_redirect + +.. doxygenfunction:: add_ostream_redirect + +Python built-in functions +========================= + +.. doxygengroup:: python_builtins + :members: + +Inheritance +=========== + +See :doc:`/classes` and :doc:`/advanced/classes` for more detail. + +.. doxygendefine:: PYBIND11_OVERRIDE + +.. doxygendefine:: PYBIND11_OVERRIDE_PURE + +.. doxygendefine:: PYBIND11_OVERRIDE_NAME + +.. doxygendefine:: PYBIND11_OVERRIDE_PURE_NAME + +.. doxygenfunction:: get_override + +Exceptions +========== + +.. doxygenclass:: error_already_set + :members: + +.. doxygenclass:: builtin_exception + :members: + +Literals +======== + +.. doxygennamespace:: literals diff --git a/extern/pybind11/docs/release.rst b/extern/pybind11/docs/release.rst new file mode 100644 index 000000000..47b5717ca --- /dev/null +++ b/extern/pybind11/docs/release.rst @@ -0,0 +1,143 @@ +On version numbers +^^^^^^^^^^^^^^^^^^ + +The two version numbers (C++ and Python) must match when combined (checked when +you build the PyPI package), and must be a valid `PEP 440 +`_ version when combined. + +For example: + +.. code-block:: C++ + + #define PYBIND11_VERSION_MAJOR X + #define PYBIND11_VERSION_MINOR Y + #define PYBIND11_VERSION_PATCH Z.dev1 + +For beta, ``PYBIND11_VERSION_PATCH`` should be ``Z.b1``. RC's can be ``Z.rc1``. +Always include the dot (even though PEP 440 allows it to be dropped). For a +final release, this must be a simple integer. There is also +``PYBIND11_VERSION_HEX`` just below that needs to be updated. + + +To release a new version of pybind11: +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +If you don't have nox, you should either use ``pipx run nox`` instead, or use +``pipx install nox`` or ``brew install nox`` (Unix). + +- Update the version number + + - Update ``PYBIND11_VERSION_MAJOR`` etc. in + ``include/pybind11/detail/common.h``. PATCH should be a simple integer. + + - Update ``PYBIND11_VERSION_HEX`` just below as well. + + - Update ``pybind11/_version.py`` (match above). + + - 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`` and integrate the output of + ``nox -s make_changelog``. + + - Note that the ``nox -s 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 + + - New branch: ``git checkout -b vX.Y``, ``git push -u origin vX.Y`` + + - Update branch: ``git checkout vX.Y``, ``git merge ``, ``git push`` + +- Update tags (optional; if you skip this, the GitHub release makes a + non-annotated tag for you) + + - ``git tag -a vX.Y.Z -m 'vX.Y.Z release'`` + + - ``grep ^__version__ pybind11/_version.py`` + + - Last-minute consistency check: same as tag? + + - ``git push --tags`` + +- Update stable + + - ``git checkout stable`` + + - ``git merge -X theirs vX.Y.Z`` + + - ``git diff vX.Y.Z`` + + - Carefully review and reconcile any diffs. There should be none. + + - ``git push`` + +- Make a GitHub release (this shows up in the UI, sends new release + notifications to users watching releases, and also uploads PyPI packages). + (Note: if you do not use an existing tag, this creates a new lightweight tag + for you, so you could skip the above step.) + + - GUI method: Under `releases `_ + click "Draft a new release" on the far right, fill in the tag name + (if you didn't tag above, it will be made here), fill in a release name + like "Version X.Y.Z", and copy-and-paste the markdown-formatted (!) changelog + into the description. You can use ``cat docs/changelog.rst | pandoc -f rst -t gfm``, + then manually remove line breaks and strip links to PRs and issues, + e.g. to a bare ``#1234``, without the surrounding ``<...>_`` hyperlink markup. + Check "pre-release" if this is a beta/RC. + + - CLI method: with ``gh`` installed, run ``gh release create vX.Y.Z -t "Version X.Y.Z"`` + If this is a pre-release, add ``-p``. + +- Get back to work + + - Make sure you are on master, not somewhere else: ``git checkout master`` + + - Update version macros in ``include/pybind11/detail/common.h`` (set PATCH to + ``0.dev1`` and increment MINOR). + + - Update ``pybind11/_version.py`` to match. + + - Run ``nox -s tests_packaging`` to ensure this was done correctly. + + - If the release was a new MINOR version, add a new ``IN DEVELOPMENT`` + section in ``docs/changelog.rst``. + + - ``git add``, ``git commit``, ``git push`` + +If a version branch is updated, remember to set PATCH to ``1.dev1``. + +If you'd like to bump homebrew, run: + +.. code-block:: console + + brew bump-formula-pr --url https://github.com/pybind/pybind11/archive/vX.Y.Z.tar.gz + +Conda-forge should automatically make a PR in a few hours, and automatically +merge it if there are no issues. + + +Manual packaging +^^^^^^^^^^^^^^^^ + +If you need to manually upload releases, you can download the releases from +the job artifacts and upload them with twine. You can also make the files +locally (not recommended in general, as your local directory is more likely +to be "dirty" and SDists love picking up random unrelated/hidden files); +this is the procedure: + +.. code-block:: bash + + nox -s build + twine upload dist/* + +This makes SDists and wheels, and the final line uploads them. diff --git a/extern/pybind11/docs/requirements.txt b/extern/pybind11/docs/requirements.txt new file mode 100644 index 000000000..d2a9ae164 --- /dev/null +++ b/extern/pybind11/docs/requirements.txt @@ -0,0 +1,6 @@ +breathe==4.34.0 +furo==2022.6.21 +sphinx==5.0.2 +sphinx-copybutton==0.5.0 +sphinxcontrib-moderncmakedomain==3.21.4 +sphinxcontrib-svg2pdfconverter==1.2.0 diff --git a/extern/pybind11/docs/upgrade.rst b/extern/pybind11/docs/upgrade.rst new file mode 100644 index 000000000..17c26aaa9 --- /dev/null +++ b/extern/pybind11/docs/upgrade.rst @@ -0,0 +1,594 @@ +Upgrade guide +############# + +This is a companion guide to the :doc:`changelog`. While the changelog briefly +lists all of the new features, improvements and bug fixes, this upgrade guide +focuses only the subset which directly impacts your experience when upgrading +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.12: + +v2.12 +===== + +NumPy support has been upgraded to support the 2.x series too. The two relevant +changes are that: + +* ``dtype.flags()`` is now a ``uint64`` and ``dtype.alignment()`` an + ``ssize_t`` (and NumPy may return an larger than integer value for + ``itemsize()`` in NumPy 2.x). + +* The long deprecated NumPy function ``PyArray_GetArrayParamsFromObject`` + function is not available anymore. + +Due to NumPy changes, you may experience difficulties updating to NumPy 2. +Please see the [NumPy 2 migration guide](https://numpy.org/devdocs/numpy_2_0_migration_guide.html) for details. +For example, a more direct change could be that the default integer ``"int_"`` +(and ``"uint"``) is now ``ssize_t`` and not ``long`` (affects 64bit windows). + +If you want to only support NumPy 1.x for now and are having problems due to +the two internal changes listed above, you can define +``PYBIND11_NUMPY_1_ONLY`` to disable the new support for now. Make sure you +define this on all pybind11 compile units, since it could be a source of ODR +violations if used inconsistently. This option will be removed in the future, +so adapting your code is highly recommended. + + +.. _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 +==== + +* Any usage of the recently added ``py::make_simple_namespace`` should be + converted to using ``py::module_::import("types").attr("SimpleNamespace")`` + instead. + +* The use of ``_`` in custom type casters can now be replaced with the more + readable ``const_name`` instead. The old ``_`` shortcut has been retained + unless it is being used as a macro (like for gettext). + + +.. _upgrade-guide-2.7: + +v2.7 +==== + +*Before* v2.7, ``py::str`` can hold ``PyUnicodeObject`` or ``PyBytesObject``, +and ``py::isinstance()`` is ``true`` for both ``py::str`` and +``py::bytes``. Starting with v2.7, ``py::str`` exclusively holds +``PyUnicodeObject`` (`#2409 `_), +and ``py::isinstance()`` is ``true`` only for ``py::str``. To help in +the transition of user code, the ``PYBIND11_STR_LEGACY_PERMISSIVE`` macro +is provided as an escape hatch to go back to the legacy behavior. This macro +will be removed in future releases. Two types of required fixes are expected +to be common: + +* Accidental use of ``py::str`` instead of ``py::bytes``, masked by the legacy + behavior. These are probably very easy to fix, by changing from + ``py::str`` to ``py::bytes``. + +* Reliance on py::isinstance(obj) being ``true`` for + ``py::bytes``. This is likely to be easy to fix in most cases by adding + ``|| py::isinstance(obj)``, but a fix may be more involved, e.g. if + ``py::isinstance`` appears in a template. Such situations will require + careful review and custom fixes. + + +.. _upgrade-guide-2.6: + +v2.6 +==== + +Usage of the ``PYBIND11_OVERLOAD*`` macros and ``get_overload`` function should +be replaced by ``PYBIND11_OVERRIDE*`` and ``get_override``. In the future, the +old macros may be deprecated and removed. + +``py::module`` has been renamed ``py::module_``, but a backward compatible +typedef has been included. This change was to avoid a language change in C++20 +that requires unqualified ``module`` not be placed at the start of a logical +line. Qualified usage is unaffected and the typedef will remain unless the +C++ language rules change again. + +The public constructors of ``py::module_`` have been deprecated. Use +``PYBIND11_MODULE`` or ``module_::create_extension_module`` instead. + +An error is now thrown when ``__init__`` is forgotten on subclasses. This was +incorrect before, but was not checked. Add a call to ``__init__`` if it is +missing. + +A ``py::type_error`` is now thrown when casting to a subclass (like +``py::bytes`` from ``py::object``) if the conversion is not valid. Make a valid +conversion instead. + +The undocumented ``h.get_type()`` method has been deprecated and replaced by +``py::type::of(h)``. + +Enums now have a ``__str__`` method pre-defined; if you want to override it, +the simplest fix is to add the new ``py::prepend()`` tag when defining +``"__str__"``. + +If ``__eq__`` defined but not ``__hash__``, ``__hash__`` is now set to +``None``, as in normal CPython. You should add ``__hash__`` if you intended the +class to be hashable, possibly using the new ``py::hash`` shortcut. + +The constructors for ``py::array`` now always take signed integers for size, +for consistency. This may lead to compiler warnings on some systems. Cast to +``py::ssize_t`` instead of ``std::size_t``. + +The ``tools/clang`` submodule and ``tools/mkdoc.py`` have been moved to a +standalone package, `pybind11-mkdoc`_. If you were using those tools, please +use them via a pip install from the new location. + +The ``pybind11`` package on PyPI no longer fills the wheel "headers" slot - if +you were using the headers from this slot, they are available by requesting the +``global`` extra, that is, ``pip install "pybind11[global]"``. (Most users will +be unaffected, as the ``pybind11/include`` location is reported by ``python -m +pybind11 --includes`` and ``pybind11.get_include()`` is still correct and has +not changed since 2.5). + +.. _pybind11-mkdoc: https://github.com/pybind/pybind11-mkdoc + +CMake support: +-------------- + +The minimum required version of CMake is now 3.4. Several details of the CMake +support have been deprecated; warnings will be shown if you need to change +something. The changes are: + +* ``PYBIND11_CPP_STANDARD=`` is deprecated, please use + ``CMAKE_CXX_STANDARD=`` instead, or any other valid CMake CXX or CUDA + standard selection method, like ``target_compile_features``. + +* If you do not request a standard, pybind11 targets will compile with the + compiler default, but not less than C++11, instead of forcing C++14 always. + If you depend on the old behavior, please use ``set(CMAKE_CXX_STANDARD 14 CACHE STRING "")`` + instead. + +* Direct ``pybind11::module`` usage should always be accompanied by at least + ``set(CMAKE_CXX_VISIBILITY_PRESET hidden)`` or similar - it used to try to + manually force this compiler flag (but not correctly on all compilers or with + CUDA). + +* ``pybind11_add_module``'s ``SYSTEM`` argument is deprecated and does nothing; + linking now behaves like other imported libraries consistently in both + config and submodule mode, and behaves like a ``SYSTEM`` library by + default. + +* If ``PYTHON_EXECUTABLE`` is not set, virtual environments (``venv``, + ``virtualenv``, and ``conda``) are prioritized over the standard search + (similar to the new FindPython mode). + +In addition, the following changes may be of interest: + +* ``CMAKE_INTERPROCEDURAL_OPTIMIZATION`` will be respected by + ``pybind11_add_module`` if set instead of linking to ``pybind11::lto`` or + ``pybind11::thin_lto``. + +* Using ``find_package(Python COMPONENTS Interpreter Development)`` before + pybind11 will cause pybind11 to use the new Python mechanisms instead of its + own custom search, based on a patched version of classic ``FindPythonInterp`` + / ``FindPythonLibs``. In the future, this may become the default. A recent + (3.15+ or 3.18.2+) version of CMake is recommended. + + + +v2.5 +==== + +The Python package now includes the headers as data in the package itself, as +well as in the "headers" wheel slot. ``pybind11 --includes`` and +``pybind11.get_include()`` report the new location, which is always correct +regardless of how pybind11 was installed, making the old ``user=`` argument +meaningless. If you are not using the function to get the location already, you +are encouraged to switch to the package location. + + +v2.2 +==== + +Deprecation of the ``PYBIND11_PLUGIN`` macro +-------------------------------------------- + +``PYBIND11_MODULE`` is now the preferred way to create module entry points. +The old macro emits a compile-time deprecation warning. + +.. code-block:: cpp + + // old + PYBIND11_PLUGIN(example) { + py::module m("example", "documentation string"); + + m.def("add", [](int a, int b) { return a + b; }); + + return m.ptr(); + } + + // new + PYBIND11_MODULE(example, m) { + m.doc() = "documentation string"; // optional + + m.def("add", [](int a, int b) { return a + b; }); + } + + +New API for defining custom constructors and pickling functions +--------------------------------------------------------------- + +The old placement-new custom constructors have been deprecated. The new approach +uses ``py::init()`` and factory functions to greatly improve type safety. + +Placement-new can be called accidentally with an incompatible type (without any +compiler errors or warnings), or it can initialize the same object multiple times +if not careful with the Python-side ``__init__`` calls. The new-style custom +constructors prevent such mistakes. See :ref:`custom_constructors` for details. + +.. code-block:: cpp + + // old -- deprecated (runtime warning shown only in debug mode) + py::class(m, "Foo") + .def("__init__", [](Foo &self, ...) { + new (&self) Foo(...); // uses placement-new + }); + + // new + py::class(m, "Foo") + .def(py::init([](...) { // Note: no `self` argument + return new Foo(...); // return by raw pointer + // or: return std::make_unique(...); // return by holder + // or: return Foo(...); // return by value (move constructor) + })); + +Mirroring the custom constructor changes, ``py::pickle()`` is now the preferred +way to get and set object state. See :ref:`pickling` for details. + +.. code-block:: cpp + + // old -- deprecated (runtime warning shown only in debug mode) + py::class(m, "Foo") + ... + .def("__getstate__", [](const Foo &self) { + return py::make_tuple(self.value1(), self.value2(), ...); + }) + .def("__setstate__", [](Foo &self, py::tuple t) { + new (&self) Foo(t[0].cast(), ...); + }); + + // new + py::class(m, "Foo") + ... + .def(py::pickle( + [](const Foo &self) { // __getstate__ + return py::make_tuple(self.value1(), self.value2(), ...); // unchanged + }, + [](py::tuple t) { // __setstate__, note: no `self` argument + return new Foo(t[0].cast(), ...); + // or: return std::make_unique(...); // return by holder + // or: return Foo(...); // return by value (move constructor) + } + )); + +For both the constructors and pickling, warnings are shown at module +initialization time (on import, not when the functions are called). +They're only visible when compiled in debug mode. Sample warning: + +.. code-block:: none + + pybind11-bound class 'mymodule.Foo' is using an old-style placement-new '__init__' + which has been deprecated. See the upgrade guide in pybind11's docs. + + +Stricter enforcement of hidden symbol visibility for pybind11 modules +--------------------------------------------------------------------- + +pybind11 now tries to actively enforce hidden symbol visibility for modules. +If you're using either one of pybind11's :doc:`CMake or Python build systems +` (the two example repositories) and you haven't been exporting any +symbols, there's nothing to be concerned about. All the changes have been done +transparently in the background. If you were building manually or relied on +specific default visibility, read on. + +Setting default symbol visibility to *hidden* has always been recommended for +pybind11 (see :ref:`faq:symhidden`). On Linux and macOS, hidden symbol +visibility (in conjunction with the ``strip`` utility) yields much smaller +module binaries. `CPython's extension docs`_ also recommend hiding symbols +by default, with the goal of avoiding symbol name clashes between modules. +Starting with v2.2, pybind11 enforces this more strictly: (1) by declaring +all symbols inside the ``pybind11`` namespace as hidden and (2) by including +the ``-fvisibility=hidden`` flag on Linux and macOS (only for extension +modules, not for embedding the interpreter). + +.. _CPython's extension docs: https://docs.python.org/3/extending/extending.html#providing-a-c-api-for-an-extension-module + +The namespace-scope hidden visibility is done automatically in pybind11's +headers and it's generally transparent to users. It ensures that: + +* Modules compiled with different pybind11 versions don't clash with each other. + +* Some new features, like ``py::module_local`` bindings, can work as intended. + +The ``-fvisibility=hidden`` flag applies the same visibility to user bindings +outside of the ``pybind11`` namespace. It's now set automatic by pybind11's +CMake and Python build systems, but this needs to be done manually by users +of other build systems. Adding this flag: + +* Minimizes the chances of symbol conflicts between modules. E.g. if two + unrelated modules were statically linked to different (ABI-incompatible) + versions of the same third-party library, a symbol clash would be likely + (and would end with unpredictable results). + +* Produces smaller binaries on Linux and macOS, as pointed out previously. + +Within pybind11's CMake build system, ``pybind11_add_module`` has always been +setting the ``-fvisibility=hidden`` flag in release mode. From now on, it's +being applied unconditionally, even in debug mode and it can no longer be opted +out of with the ``NO_EXTRAS`` option. The ``pybind11::module`` target now also +adds this flag to its interface. The ``pybind11::embed`` target is unchanged. + +The most significant change here is for the ``pybind11::module`` target. If you +were previously relying on default visibility, i.e. if your Python module was +doubling as a shared library with dependents, you'll need to either export +symbols manually (recommended for cross-platform libraries) or factor out the +shared library (and have the Python module link to it like the other +dependents). As a temporary workaround, you can also restore default visibility +using the CMake code below, but this is not recommended in the long run: + +.. code-block:: cmake + + target_link_libraries(mymodule PRIVATE pybind11::module) + + add_library(restore_default_visibility INTERFACE) + target_compile_options(restore_default_visibility INTERFACE -fvisibility=default) + target_link_libraries(mymodule PRIVATE restore_default_visibility) + + +Local STL container bindings +---------------------------- + +Previous pybind11 versions could only bind types globally -- all pybind11 +modules, even unrelated ones, would have access to the same exported types. +However, this would also result in a conflict if two modules exported the +same C++ type, which is especially problematic for very common types, e.g. +``std::vector``. :ref:`module_local` were added to resolve this (see +that section for a complete usage guide). + +``py::class_`` still defaults to global bindings (because these types are +usually unique across modules), however in order to avoid clashes of opaque +types, ``py::bind_vector`` and ``py::bind_map`` will now bind STL containers +as ``py::module_local`` if their elements are: builtins (``int``, ``float``, +etc.), not bound using ``py::class_``, or bound as ``py::module_local``. For +example, this change allows multiple modules to bind ``std::vector`` +without causing conflicts. See :ref:`stl_bind` for more details. + +When upgrading to this version, if you have multiple modules which depend on +a single global binding of an STL container, note that all modules can still +accept foreign ``py::module_local`` types in the direction of Python-to-C++. +The locality only affects the C++-to-Python direction. If this is needed in +multiple modules, you'll need to either: + +* Add a copy of the same STL binding to all of the modules which need it. + +* Restore the global status of that single binding by marking it + ``py::module_local(false)``. + +The latter is an easy workaround, but in the long run it would be best to +localize all common type bindings in order to avoid conflicts with +third-party modules. + + +Negative strides for Python buffer objects and numpy arrays +----------------------------------------------------------- + +Support for negative strides required changing the integer type from unsigned +to signed in the interfaces of ``py::buffer_info`` and ``py::array``. If you +have compiler warnings enabled, you may notice some new conversion warnings +after upgrading. These can be resolved using ``static_cast``. + + +Deprecation of some ``py::object`` APIs +--------------------------------------- + +To compare ``py::object`` instances by pointer, you should now use +``obj1.is(obj2)`` which is equivalent to ``obj1 is obj2`` in Python. +Previously, pybind11 used ``operator==`` for this (``obj1 == obj2``), but +that could be confusing and is now deprecated (so that it can eventually +be replaced with proper rich object comparison in a future release). + +For classes which inherit from ``py::object``, ``borrowed`` and ``stolen`` +were previously available as protected constructor tags. Now the types +should be used directly instead: ``borrowed_t{}`` and ``stolen_t{}`` +(`#771 `_). + + +Stricter compile-time error checking +------------------------------------ + +Some error checks have been moved from run time to compile time. Notably, +automatic conversion of ``std::shared_ptr`` is not possible when ``T`` is +not directly registered with ``py::class_`` (e.g. ``std::shared_ptr`` +or ``std::shared_ptr>`` are not automatically convertible). +Attempting to bind a function with such arguments now results in a compile-time +error instead of waiting to fail at run time. + +``py::init<...>()`` constructor definitions are also stricter and now prevent +bindings which could cause unexpected behavior: + +.. code-block:: cpp + + struct Example { + Example(int &); + }; + + py::class_(m, "Example") + .def(py::init()); // OK, exact match + // .def(py::init()); // compile-time error, mismatch + +A non-``const`` lvalue reference is not allowed to bind to an rvalue. However, +note that a constructor taking ``const T &`` can still be registered using +``py::init()`` because a ``const`` lvalue reference can bind to an rvalue. + +v2.1 +==== + +Minimum compiler versions are enforced at compile time +------------------------------------------------------ + +The minimums also apply to v2.0 but the check is now explicit and a compile-time +error is raised if the compiler does not meet the requirements: + +* GCC >= 4.8 +* clang >= 3.3 (appleclang >= 5.0) +* MSVC >= 2015u3 +* Intel C++ >= 15.0 + + +The ``py::metaclass`` attribute is not required for static properties +--------------------------------------------------------------------- + +Binding classes with static properties is now possible by default. The +zero-parameter version of ``py::metaclass()`` is deprecated. However, a new +one-parameter ``py::metaclass(python_type)`` version was added for rare +cases when a custom metaclass is needed to override pybind11's default. + +.. code-block:: cpp + + // old -- emits a deprecation warning + py::class_(m, "Foo", py::metaclass()) + .def_property_readonly_static("foo", ...); + + // new -- static properties work without the attribute + py::class_(m, "Foo") + .def_property_readonly_static("foo", ...); + + // new -- advanced feature, override pybind11's default metaclass + py::class_(m, "Bar", py::metaclass(custom_python_type)) + ... + + +v2.0 +==== + +Breaking changes in ``py::class_`` +---------------------------------- + +These changes were necessary to make type definitions in pybind11 +future-proof, to support PyPy via its ``cpyext`` mechanism (`#527 +`_), and to improve efficiency +(`rev. 86d825 `_). + +1. Declarations of types that provide access via the buffer protocol must + now include the ``py::buffer_protocol()`` annotation as an argument to + the ``py::class_`` constructor. + + .. code-block:: cpp + + py::class_("Matrix", py::buffer_protocol()) + .def(py::init<...>()) + .def_buffer(...); + +2. Classes which include static properties (e.g. ``def_readwrite_static()``) + must now include the ``py::metaclass()`` attribute. Note: this requirement + has since been removed in v2.1. If you're upgrading from 1.x, it's + recommended to skip directly to v2.1 or newer. + +3. This version of pybind11 uses a redesigned mechanism for instantiating + trampoline classes that are used to override virtual methods from within + Python. This led to the following user-visible syntax change: + + .. code-block:: cpp + + // old v1.x syntax + py::class_("MyClass") + .alias() + ... + + // new v2.x syntax + py::class_("MyClass") + ... + + Importantly, both the original and the trampoline class are now specified + as arguments to the ``py::class_`` template, and the ``alias<..>()`` call + is gone. The new scheme has zero overhead in cases when Python doesn't + override any functions of the underlying C++ class. + `rev. 86d825 `_. + + The class type must be the first template argument given to ``py::class_`` + while the trampoline can be mixed in arbitrary order with other arguments + (see the following section). + + +Deprecation of the ``py::base()`` attribute +---------------------------------------------- + +``py::base()`` was deprecated in favor of specifying ``T`` as a template +argument to ``py::class_``. This new syntax also supports multiple inheritance. +Note that, while the type being exported must be the first argument in the +``py::class_`` template, the order of the following types (bases, +holder and/or trampoline) is not important. + +.. code-block:: cpp + + // old v1.x + py::class_("Derived", py::base()); + + // new v2.x + py::class_("Derived"); + + // new -- multiple inheritance + py::class_("Derived"); + + // new -- apart from `Derived` the argument order can be arbitrary + py::class_("Derived"); + + +Out-of-the-box support for ``std::shared_ptr`` +---------------------------------------------- + +The relevant type caster is now built in, so it's no longer necessary to +include a declaration of the form: + +.. code-block:: cpp + + PYBIND11_DECLARE_HOLDER_TYPE(T, std::shared_ptr) + +Continuing to do so won't cause an error or even a deprecation warning, +but it's completely redundant. + + +Deprecation of a few ``py::object`` APIs +---------------------------------------- + +All of the old-style calls emit deprecation warnings. + ++---------------------------------------+---------------------------------------------+ +| Old syntax | New syntax | ++=======================================+=============================================+ +| ``obj.call(args...)`` | ``obj(args...)`` | ++---------------------------------------+---------------------------------------------+ +| ``obj.str()`` | ``py::str(obj)`` | ++---------------------------------------+---------------------------------------------+ +| ``auto l = py::list(obj); l.check()`` | ``py::isinstance(obj)`` | ++---------------------------------------+---------------------------------------------+ +| ``py::object(ptr, true)`` | ``py::reinterpret_borrow(ptr)`` | ++---------------------------------------+---------------------------------------------+ +| ``py::object(ptr, false)`` | ``py::reinterpret_steal(ptr)`` | ++---------------------------------------+---------------------------------------------+ +| ``if (obj.attr("foo"))`` | ``if (py::hasattr(obj, "foo"))`` | ++---------------------------------------+---------------------------------------------+ +| ``if (obj["bar"])`` | ``if (obj.contains("bar"))`` | ++---------------------------------------+---------------------------------------------+ diff --git a/extern/pybind11/include/pybind11/attr.h b/extern/pybind11/include/pybind11/attr.h new file mode 100644 index 000000000..1044db94d --- /dev/null +++ b/extern/pybind11/include/pybind11/attr.h @@ -0,0 +1,690 @@ +/* + pybind11/attr.h: Infrastructure for processing custom + type and function attributes + + 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 "detail/common.h" +#include "cast.h" + +#include + +PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE) + +/// \addtogroup annotations +/// @{ + +/// Annotation for methods +struct is_method { + handle class_; + explicit is_method(const handle &c) : class_(c) {} +}; + +/// Annotation for setters +struct is_setter {}; + +/// Annotation for operators +struct is_operator {}; + +/// Annotation for classes that cannot be subclassed +struct is_final {}; + +/// Annotation for parent scope +struct scope { + handle value; + explicit scope(const handle &s) : value(s) {} +}; + +/// Annotation for documentation +struct doc { + const char *value; + explicit doc(const char *value) : value(value) {} +}; + +/// Annotation for function names +struct name { + const char *value; + explicit name(const char *value) : value(value) {} +}; + +/// Annotation indicating that a function is an overload associated with a given "sibling" +struct sibling { + handle value; + explicit sibling(const handle &value) : value(value.ptr()) {} +}; + +/// Annotation indicating that a class derives from another given type +template +struct base { + + PYBIND11_DEPRECATED( + "base() was deprecated in favor of specifying 'T' as a template argument to class_") + base() = default; +}; + +/// Keep patient alive while nurse lives +template +struct keep_alive {}; + +/// Annotation indicating that a class is involved in a multiple inheritance relationship +struct multiple_inheritance {}; + +/// Annotation which enables dynamic attributes, i.e. adds `__dict__` to a class +struct dynamic_attr {}; + +/// Annotation which enables the buffer protocol for a type +struct buffer_protocol {}; + +/// Annotation which requests that a special metaclass is created for a type +struct metaclass { + handle value; + + PYBIND11_DEPRECATED("py::metaclass() is no longer required. It's turned on by default now.") + metaclass() = default; + + /// Override pybind11's default metaclass + explicit metaclass(handle value) : value(value) {} +}; + +/// Specifies a custom callback with signature `void (PyHeapTypeObject*)` that +/// may be used to customize the Python type. +/// +/// The callback is invoked immediately before `PyType_Ready`. +/// +/// Note: This is an advanced interface, and uses of it may require changes to +/// work with later versions of pybind11. You may wish to consult the +/// implementation of `make_new_python_type` in `detail/classes.h` to understand +/// the context in which the callback will be run. +struct custom_type_setup { + using callback = std::function; + + explicit custom_type_setup(callback value) : value(std::move(value)) {} + + callback value; +}; + +/// Annotation that marks a class as local to the module: +struct module_local { + const bool value; + constexpr explicit module_local(bool v = true) : value(v) {} +}; + +/// Annotation to mark enums as an arithmetic type +struct arithmetic {}; + +/// Mark a function for addition at the beginning of the existing overload chain instead of the end +struct prepend {}; + +/** \rst + A call policy which places one or more guard variables (``Ts...``) around the function call. + + For example, this definition: + + .. code-block:: cpp + + m.def("foo", foo, py::call_guard()); + + is equivalent to the following pseudocode: + + .. code-block:: cpp + + m.def("foo", [](args...) { + T scope_guard; + return foo(args...); // forwarded arguments + }); + \endrst */ +template +struct call_guard; + +template <> +struct call_guard<> { + using type = detail::void_type; +}; + +template +struct call_guard { + static_assert(std::is_default_constructible::value, + "The guard type must be default constructible"); + + using type = T; +}; + +template +struct call_guard { + struct type { + T guard{}; // Compose multiple guard types with left-to-right default-constructor order + typename call_guard::type next{}; + }; +}; + +/// @} annotations + +PYBIND11_NAMESPACE_BEGIN(detail) +/* Forward declarations */ +enum op_id : int; +enum op_type : int; +struct undefined_t; +template +struct op_; +void keep_alive_impl(size_t Nurse, size_t Patient, function_call &call, handle ret); + +/// Internal data structure which holds metadata about a keyword argument +struct argument_record { + const char *name; ///< Argument name + const char *descr; ///< Human-readable version of the argument value + handle value; ///< Associated Python object + bool convert : 1; ///< True if the argument is allowed to convert when loading + bool none : 1; ///< True if None is allowed when loading + + argument_record(const char *name, const char *descr, handle value, bool convert, bool none) + : name(name), descr(descr), value(value), convert(convert), none(none) {} +}; + +/// Internal data structure which holds metadata about a bound function (signature, overloads, +/// etc.) +struct function_record { + function_record() + : is_constructor(false), is_new_style_constructor(false), is_stateless(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.. */ + + // User-specified documentation string + char *doc = nullptr; + + /// Human-readable version of the function signature + char *signature = nullptr; + + /// List of registered keyword arguments + std::vector args; + + /// Pointer to lambda function which converts arguments and performs the actual call + handle (*impl)(function_call &) = nullptr; + + /// Storage for the wrapped function pointer and captured data, if any + void *data[3] = {}; + + /// Pointer to custom destructor for 'data' (if needed) + void (*free_data)(function_record *ptr) = nullptr; + + /// Return value policy associated with this function + return_value_policy policy = return_value_policy::automatic; + + /// True if name == '__init__' + bool is_constructor : 1; + + /// True if this is a new-style `__init__` defined in `detail/init.h` + bool is_new_style_constructor : 1; + + /// True if this is a stateless function pointer + bool is_stateless : 1; + + /// True if this is an operator (__add__), etc. + bool is_operator : 1; + + /// 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; + + /// True if the function has a '**kwargs' argument + bool has_kwargs : 1; + + /// True if this function is to be inserted at the beginning of the overload resolution chain + bool prepend : 1; + + /// Number of arguments (including py::args and/or py::kwargs, if present) + std::uint16_t nargs; + + /// Number of leading positional arguments, which are terminated by a py::args or py::kwargs + /// argument or by a py::kw_only annotation. + std::uint16_t nargs_pos = 0; + + /// Number of leading arguments (counted in `nargs`) that are positional-only + std::uint16_t nargs_pos_only = 0; + + /// Python method object + PyMethodDef *def = nullptr; + + /// Python handle to the parent scope (a class or a module) + handle scope; + + /// Python handle to the sibling function representing an overload chain + handle sibling; + + /// Pointer to next overload + function_record *next = nullptr; +}; + +/// Special data structure which (temporarily) holds metadata about a bound class +struct type_record { + PYBIND11_NOINLINE type_record() + : multiple_inheritance(false), dynamic_attr(false), buffer_protocol(false), + default_holder(true), module_local(false), is_final(false) {} + + /// Handle to the parent scope + handle scope; + + /// Name of the class + const char *name = nullptr; + + // Pointer to RTTI type_info data structure + const std::type_info *type = nullptr; + + /// How large is the underlying C++ type? + size_t type_size = 0; + + /// What is the alignment of the underlying C++ type? + size_t type_align = 0; + + /// How large is the type's holder? + size_t holder_size = 0; + + /// The global operator new can be overridden with a class-specific variant + void *(*operator_new)(size_t) = nullptr; + + /// Function pointer to class_<..>::init_instance + void (*init_instance)(instance *, const void *) = nullptr; + + /// Function pointer to class_<..>::dealloc + void (*dealloc)(detail::value_and_holder &) = nullptr; + + /// List of base classes of the newly created type + list bases; + + /// Optional docstring + const char *doc = nullptr; + + /// Custom metaclass (optional) + handle metaclass; + + /// Custom type setup. + custom_type_setup::callback custom_type_setup_callback; + + /// Multiple inheritance marker + bool multiple_inheritance : 1; + + /// Does the class manage a __dict__? + bool dynamic_attr : 1; + + /// Does the class implement the buffer protocol? + bool buffer_protocol : 1; + + /// Is the default (unique_ptr) holder type used? + bool default_holder : 1; + + /// Is the class definition local to the module shared object? + bool module_local : 1; + + /// Is the class inheritable from python classes? + bool is_final : 1; + + PYBIND11_NOINLINE void add_base(const std::type_info &base, void *(*caster)(void *) ) { + auto *base_info = detail::get_type_info(base, false); + if (!base_info) { + std::string tname(base.name()); + detail::clean_type_id(tname); + pybind11_fail("generic_type: type \"" + std::string(name) + + "\" referenced unknown base type \"" + tname + "\""); + } + + if (default_holder != base_info->default_holder) { + std::string tname(base.name()); + detail::clean_type_id(tname); + pybind11_fail("generic_type: type \"" + std::string(name) + "\" " + + (default_holder ? "does not have" : "has") + + " a non-default holder type while its base \"" + tname + "\" " + + (base_info->default_holder ? "does not" : "does")); + } + + bases.append((PyObject *) base_info->type); + +#if PY_VERSION_HEX < 0x030B0000 + dynamic_attr |= base_info->type->tp_dictoffset != 0; +#else + dynamic_attr |= (base_info->type->tp_flags & Py_TPFLAGS_MANAGED_DICT) != 0; +#endif + + if (caster) { + base_info->implicit_casts.emplace_back(type, caster); + } + } +}; + +inline function_call::function_call(const function_record &f, handle p) : func(f), parent(p) { + args.reserve(f.nargs); + args_convert.reserve(f.nargs); +} + +/// Tag for a new-style `__init__` defined in `detail/init.h` +struct is_new_style_constructor {}; + +/** + * Partial template specializations to process custom attributes provided to + * cpp_function_ and class_. These are either used to initialize the respective + * fields in the type_record and function_record data structures or executed at + * runtime to deal with custom call policies (e.g. keep_alive). + */ +template +struct process_attribute; + +template +struct process_attribute_default { + /// Default implementation: do nothing + static void init(const T &, function_record *) {} + static void init(const T &, type_record *) {} + static void precall(function_call &) {} + static void postcall(function_call &, handle) {} +}; + +/// Process an attribute specifying the function's name +template <> +struct process_attribute : process_attribute_default { + static void init(const name &n, function_record *r) { r->name = const_cast(n.value); } +}; + +/// Process an attribute specifying the function's docstring +template <> +struct process_attribute : process_attribute_default { + static void init(const doc &n, function_record *r) { r->doc = const_cast(n.value); } +}; + +/// Process an attribute specifying the function's docstring (provided as a C-style string) +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 = d; } +}; +template <> +struct process_attribute : process_attribute {}; + +/// Process an attribute indicating the function's return value policy +template <> +struct process_attribute : process_attribute_default { + static void init(const return_value_policy &p, function_record *r) { r->policy = p; } +}; + +/// Process an attribute which indicates that this is an overloaded function associated with a +/// given sibling +template <> +struct process_attribute : process_attribute_default { + static void init(const sibling &s, function_record *r) { r->sibling = s.value; } +}; + +/// Process an attribute which indicates that this function is a method +template <> +struct process_attribute : process_attribute_default { + static void init(const is_method &s, function_record *r) { + r->is_method = true; + r->scope = s.class_; + } +}; + +/// 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 { + static void init(const scope &s, function_record *r) { r->scope = s.value; } +}; + +/// Process an attribute which indicates that this function is an operator +template <> +struct process_attribute : process_attribute_default { + static void init(const is_operator &, function_record *r) { r->is_operator = true; } +}; + +template <> +struct process_attribute + : process_attribute_default { + static void init(const is_new_style_constructor &, function_record *r) { + r->is_new_style_constructor = true; + } +}; + +inline void check_kw_only_arg(const arg &a, function_record *r) { + if (r->args.size() > r->nargs_pos && (!a.name || a.name[0] == '\0')) { + pybind11_fail("arg(): cannot specify an unnamed argument after a kw_only() annotation or " + "args() argument"); + } +} + +inline void append_self_arg_if_needed(function_record *r) { + if (r->is_method && r->args.empty()) { + r->args.emplace_back("self", nullptr, handle(), /*convert=*/true, /*none=*/false); + } +} + +/// Process a keyword argument attribute (*without* a default value) +template <> +struct process_attribute : process_attribute_default { + static void init(const arg &a, function_record *r) { + append_self_arg_if_needed(r); + r->args.emplace_back(a.name, nullptr, handle(), !a.flag_noconvert, a.flag_none); + + check_kw_only_arg(a, r); + } +}; + +/// Process a keyword argument attribute (*with* a default value) +template <> +struct process_attribute : process_attribute_default { + static void init(const arg_v &a, function_record *r) { + if (r->is_method && r->args.empty()) { + r->args.emplace_back( + "self", /*descr=*/nullptr, /*parent=*/handle(), /*convert=*/true, /*none=*/false); + } + + if (!a.value) { +#if defined(PYBIND11_DETAILED_ERROR_MESSAGES) + std::string descr("'"); + if (a.name) { + descr += std::string(a.name) + ": "; + } + descr += a.type + "'"; + if (r->is_method) { + if (r->name) { + descr += " in method '" + (std::string) str(r->scope) + "." + + (std::string) r->name + "'"; + } else { + descr += " in method of '" + (std::string) str(r->scope) + "'"; + } + } else if (r->name) { + descr += " in function '" + (std::string) r->name + "'"; + } + pybind11_fail("arg(): could not convert default argument " + descr + + " into a Python object (type not registered yet?)"); +#else + pybind11_fail("arg(): could not convert default argument " + "into a Python object (type not registered yet?). " + "#define PYBIND11_DETAILED_ERROR_MESSAGES or compile in debug mode for " + "more information."); +#endif + } + r->args.emplace_back(a.name, a.descr, a.value.inc_ref(), !a.flag_noconvert, a.flag_none); + + check_kw_only_arg(a, r); + } +}; + +/// Process a keyword-only-arguments-follow pseudo argument +template <> +struct process_attribute : process_attribute_default { + static void init(const kw_only &, function_record *r) { + append_self_arg_if_needed(r); + if (r->has_args && r->nargs_pos != static_cast(r->args.size())) { + pybind11_fail("Mismatched args() and kw_only(): they must occur at the same relative " + "argument location (or omit kw_only() entirely)"); + } + r->nargs_pos = static_cast(r->args.size()); + } +}; + +/// Process a positional-only-argument maker +template <> +struct process_attribute : process_attribute_default { + static void init(const pos_only &, function_record *r) { + append_self_arg_if_needed(r); + r->nargs_pos_only = static_cast(r->args.size()); + if (r->nargs_pos_only > r->nargs_pos) { + pybind11_fail("pos_only(): cannot follow a py::args() argument"); + } + // It also can't follow a kw_only, but a static_assert in pybind11.h checks that + } +}; + +/// Process a parent class attribute. Single inheritance only (class_ itself already guarantees +/// that) +template +struct process_attribute::value>> + : process_attribute_default { + static void init(const handle &h, type_record *r) { r->bases.append(h); } +}; + +/// Process a parent class attribute (deprecated, does not support multiple inheritance) +template +struct process_attribute> : process_attribute_default> { + static void init(const base &, type_record *r) { r->add_base(typeid(T), nullptr); } +}; + +/// Process a multiple inheritance attribute +template <> +struct process_attribute : process_attribute_default { + static void init(const multiple_inheritance &, type_record *r) { + r->multiple_inheritance = true; + } +}; + +template <> +struct process_attribute : process_attribute_default { + static void init(const dynamic_attr &, type_record *r) { r->dynamic_attr = true; } +}; + +template <> +struct process_attribute { + static void init(const custom_type_setup &value, type_record *r) { + r->custom_type_setup_callback = value.value; + } +}; + +template <> +struct process_attribute : process_attribute_default { + static void init(const is_final &, type_record *r) { r->is_final = true; } +}; + +template <> +struct process_attribute : process_attribute_default { + static void init(const buffer_protocol &, type_record *r) { r->buffer_protocol = true; } +}; + +template <> +struct process_attribute : process_attribute_default { + static void init(const metaclass &m, type_record *r) { r->metaclass = m.value; } +}; + +template <> +struct process_attribute : process_attribute_default { + static void init(const module_local &l, type_record *r) { r->module_local = l.value; } +}; + +/// Process a 'prepend' attribute, putting this at the beginning of the overload chain +template <> +struct process_attribute : process_attribute_default { + static void init(const prepend &, function_record *r) { r->prepend = true; } +}; + +/// Process an 'arithmetic' attribute for enums (does nothing here) +template <> +struct process_attribute : process_attribute_default {}; + +template +struct process_attribute> : process_attribute_default> {}; + +/** + * Process a keep_alive call policy -- invokes keep_alive_impl during the + * pre-call handler if both Nurse, Patient != 0 and use the post-call handler + * otherwise + */ +template +struct process_attribute> + : public process_attribute_default> { + template = 0> + static void precall(function_call &call) { + keep_alive_impl(Nurse, Patient, call, handle()); + } + template = 0> + static void postcall(function_call &, handle) {} + template = 0> + static void precall(function_call &) {} + template = 0> + static void postcall(function_call &call, handle ret) { + keep_alive_impl(Nurse, Patient, call, ret); + } +}; + +/// Recursively iterate over variadic template arguments +template +struct process_attributes { + static void init(const Args &...args, function_record *r) { + PYBIND11_WORKAROUND_INCORRECT_MSVC_C4100(r); + PYBIND11_WORKAROUND_INCORRECT_GCC_UNUSED_BUT_SET_PARAMETER(r); + using expander = int[]; + (void) expander{ + 0, ((void) process_attribute::type>::init(args, r), 0)...}; + } + static void init(const Args &...args, type_record *r) { + PYBIND11_WORKAROUND_INCORRECT_MSVC_C4100(r); + PYBIND11_WORKAROUND_INCORRECT_GCC_UNUSED_BUT_SET_PARAMETER(r); + using expander = int[]; + (void) expander{0, + (process_attribute::type>::init(args, r), 0)...}; + } + static void precall(function_call &call) { + PYBIND11_WORKAROUND_INCORRECT_MSVC_C4100(call); + using expander = int[]; + (void) expander{0, + (process_attribute::type>::precall(call), 0)...}; + } + static void postcall(function_call &call, handle fn_ret) { + PYBIND11_WORKAROUND_INCORRECT_MSVC_C4100(call, fn_ret); + PYBIND11_WORKAROUND_INCORRECT_GCC_UNUSED_BUT_SET_PARAMETER(fn_ret); + using expander = int[]; + (void) expander{ + 0, (process_attribute::type>::postcall(call, fn_ret), 0)...}; + } +}; + +template +using is_call_guard = is_instantiation; + +/// Extract the ``type`` from the first `call_guard` in `Extras...` (or `void_type` if none found) +template +using extract_guard_t = typename exactly_one_t, Extra...>::type; + +/// Check the number of named arguments at compile time +template ::value...), + size_t self = constexpr_sum(std::is_same::value...)> +constexpr bool expected_num_args(size_t nargs, bool has_args, bool has_kwargs) { + PYBIND11_WORKAROUND_INCORRECT_MSVC_C4100(nargs, has_args, has_kwargs); + return named == 0 || (self + named + size_t(has_args) + size_t(has_kwargs)) == nargs; +} + +PYBIND11_NAMESPACE_END(detail) +PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE) diff --git a/extern/pybind11/include/pybind11/buffer_info.h b/extern/pybind11/include/pybind11/buffer_info.h new file mode 100644 index 000000000..b99ee8bef --- /dev/null +++ b/extern/pybind11/include/pybind11/buffer_info.h @@ -0,0 +1,208 @@ +/* + pybind11/buffer_info.h: Python buffer object interface + + 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 "detail/common.h" + +PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE) + +PYBIND11_NAMESPACE_BEGIN(detail) + +// Default, C-style strides +inline std::vector c_strides(const std::vector &shape, ssize_t itemsize) { + auto ndim = shape.size(); + std::vector strides(ndim, itemsize); + if (ndim > 0) { + for (size_t i = ndim - 1; i > 0; --i) { + strides[i - 1] = strides[i] * shape[i]; + } + } + return strides; +} + +// F-style strides; default when constructing an array_t with `ExtraFlags & f_style` +inline std::vector f_strides(const std::vector &shape, ssize_t itemsize) { + auto ndim = shape.size(); + std::vector strides(ndim, itemsize); + for (size_t i = 1; i < ndim; ++i) { + strides[i] = strides[i - 1] * shape[i - 1]; + } + return strides; +} + +template +struct compare_buffer_info; + +PYBIND11_NAMESPACE_END(detail) + +/// Information record describing a Python buffer object +struct buffer_info { + void *ptr = nullptr; // Pointer to the underlying storage + ssize_t itemsize = 0; // Size of individual items in bytes + ssize_t size = 0; // Total number of entries + std::string format; // For homogeneous buffers, this should be set to + // format_descriptor::format() + ssize_t ndim = 0; // Number of dimensions + std::vector shape; // Shape of the tensor (1 entry per dimension) + std::vector strides; // Number of bytes between adjacent entries + // (for each per dimension) + bool readonly = false; // flag to indicate if the underlying storage may be written to + + buffer_info() = default; + + buffer_info(void *ptr, + ssize_t itemsize, + const std::string &format, + ssize_t ndim, + detail::any_container shape_in, + detail::any_container strides_in, + bool readonly = false) + : ptr(ptr), itemsize(itemsize), size(1), format(format), ndim(ndim), + shape(std::move(shape_in)), strides(std::move(strides_in)), readonly(readonly) { + if (ndim != (ssize_t) shape.size() || ndim != (ssize_t) strides.size()) { + pybind11_fail("buffer_info: ndim doesn't match shape and/or strides length"); + } + for (size_t i = 0; i < (size_t) ndim; ++i) { + size *= shape[i]; + } + } + + template + buffer_info(T *ptr, + detail::any_container shape_in, + detail::any_container strides_in, + bool readonly = false) + : buffer_info(private_ctr_tag(), + ptr, + sizeof(T), + format_descriptor::format(), + static_cast(shape_in->size()), + std::move(shape_in), + std::move(strides_in), + readonly) {} + + buffer_info(void *ptr, + ssize_t itemsize, + const std::string &format, + ssize_t size, + bool readonly = false) + : buffer_info(ptr, itemsize, format, 1, {size}, {itemsize}, readonly) {} + + template + buffer_info(T *ptr, ssize_t size, bool readonly = false) + : buffer_info(ptr, sizeof(T), format_descriptor::format(), size, readonly) {} + + template + buffer_info(const T *ptr, ssize_t size, bool readonly = true) + : buffer_info( + const_cast(ptr), sizeof(T), format_descriptor::format(), size, readonly) {} + + explicit buffer_info(Py_buffer *view, bool ownview = true) + : buffer_info( + view->buf, + view->itemsize, + view->format, + view->ndim, + {view->shape, view->shape + view->ndim}, + /* Though buffer::request() requests PyBUF_STRIDES, ctypes objects + * ignore this flag and return a view with NULL strides. + * When strides are NULL, build them manually. */ + view->strides + ? std::vector(view->strides, view->strides + view->ndim) + : detail::c_strides({view->shape, view->shape + view->ndim}, view->itemsize), + (view->readonly != 0)) { + // NOLINTNEXTLINE(cppcoreguidelines-prefer-member-initializer) + this->m_view = view; + // NOLINTNEXTLINE(cppcoreguidelines-prefer-member-initializer) + this->ownview = ownview; + } + + buffer_info(const buffer_info &) = delete; + buffer_info &operator=(const buffer_info &) = delete; + + buffer_info(buffer_info &&other) noexcept { (*this) = std::move(other); } + + buffer_info &operator=(buffer_info &&rhs) noexcept { + ptr = rhs.ptr; + itemsize = rhs.itemsize; + size = rhs.size; + format = std::move(rhs.format); + ndim = rhs.ndim; + shape = std::move(rhs.shape); + strides = std::move(rhs.strides); + std::swap(m_view, rhs.m_view); + std::swap(ownview, rhs.ownview); + readonly = rhs.readonly; + return *this; + } + + ~buffer_info() { + if (m_view && ownview) { + PyBuffer_Release(m_view); + delete m_view; + } + } + + 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 {}; + + buffer_info(private_ctr_tag, + void *ptr, + ssize_t itemsize, + const std::string &format, + ssize_t ndim, + detail::any_container &&shape_in, + detail::any_container &&strides_in, + bool readonly) + : buffer_info( + ptr, itemsize, format, ndim, std::move(shape_in), std::move(strides_in), readonly) {} + + Py_buffer *m_view = nullptr; + bool ownview = false; +}; + +PYBIND11_NAMESPACE_BEGIN(detail) + +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); + } +}; + +template +struct compare_buffer_info::value>> { + static bool compare(const buffer_info &b) { + return (size_t) b.itemsize == sizeof(T) + && (b.format == format_descriptor::value + || ((sizeof(T) == sizeof(long)) + && b.format == (std::is_unsigned::value ? "L" : "l")) + || ((sizeof(T) == sizeof(size_t)) + && b.format == (std::is_unsigned::value ? "N" : "n"))); + } +}; + +PYBIND11_NAMESPACE_END(detail) +PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE) diff --git a/extern/pybind11/include/pybind11/cast.h b/extern/pybind11/include/pybind11/cast.h new file mode 100644 index 000000000..02d9488da --- /dev/null +++ b/extern/pybind11/include/pybind11/cast.h @@ -0,0 +1,1837 @@ +/* + pybind11/cast.h: Partial template specializations to cast between + C++ and Python 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. +*/ + +#pragma once + +#include "detail/common.h" +#include "detail/descr.h" +#include "detail/type_caster_base.h" +#include "detail/typeid.h" +#include "pytypes.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE) + +PYBIND11_WARNING_DISABLE_MSVC(4127) + +PYBIND11_NAMESPACE_BEGIN(detail) + +template +class type_caster : public type_caster_base {}; +template +using make_caster = type_caster>; + +// Shortcut for calling a caster's `cast_op_type` cast operator for casting a type_caster to a T +template +typename make_caster::template cast_op_type cast_op(make_caster &caster) { + using result_t = typename make_caster::template cast_op_type; // See PR #4893 + return caster.operator result_t(); +} +template +typename make_caster::template cast_op_type::type> +cast_op(make_caster &&caster) { + using result_t = typename make_caster::template cast_op_type< + typename std::add_rvalue_reference::type>; // See PR #4893 + return std::move(caster).operator result_t(); +} + +template +class type_caster> { +private: + using caster_t = make_caster; + caster_t subcaster; + using reference_t = type &; + using subcaster_cast_op_type = typename caster_t::template cast_op_type; + + static_assert( + std::is_same::type &, subcaster_cast_op_type>::value + || std::is_same::value, + "std::reference_wrapper caster requires T to have a caster with an " + "`operator T &()` or `operator const T &()`"); + +public: + bool load(handle src, bool convert) { return subcaster.load(src, convert); } + static constexpr auto name = caster_t::name; + static handle + cast(const std::reference_wrapper &src, return_value_policy policy, handle parent) { + // It is definitely wrong to take ownership of this pointer, so mask that rvp + if (policy == return_value_policy::take_ownership + || policy == return_value_policy::automatic) { + policy = return_value_policy::automatic_reference; + } + return caster_t::cast(&src.get(), policy, parent); + } + template + using cast_op_type = std::reference_wrapper; + explicit operator std::reference_wrapper() { return cast_op(subcaster); } +}; + +#define PYBIND11_TYPE_CASTER(type, py_name) \ +protected: \ + type value; \ + \ +public: \ + static constexpr auto name = py_name; \ + template >::value, \ + int> \ + = 0> \ + static ::pybind11::handle cast( \ + T_ *src, ::pybind11::return_value_policy policy, ::pybind11::handle parent) { \ + if (!src) \ + return ::pybind11::none().release(); \ + if (policy == ::pybind11::return_value_policy::take_ownership) { \ + auto h = cast(std::move(*src), policy, parent); \ + delete src; \ + return h; \ + } \ + return cast(*src, policy, parent); \ + } \ + operator type *() { return &value; } /* NOLINT(bugprone-macro-parentheses) */ \ + operator type &() { return value; } /* NOLINT(bugprone-macro-parentheses) */ \ + operator type &&() && { return std::move(value); } /* NOLINT(bugprone-macro-parentheses) */ \ + template \ + using cast_op_type = ::pybind11::detail::movable_cast_op_type + +template +using is_std_char_type = any_of, /* std::string */ +#if defined(PYBIND11_HAS_U8STRING) + std::is_same, /* std::u8string */ +#endif + std::is_same, /* std::u16string */ + std::is_same, /* std::u32string */ + std::is_same /* std::wstring */ + >; + +template +struct type_caster::value && !is_std_char_type::value>> { + using _py_type_0 = conditional_t; + using _py_type_1 = conditional_t::value, + _py_type_0, + typename std::make_unsigned<_py_type_0>::type>; + using py_type = conditional_t::value, double, _py_type_1>; + +public: + bool load(handle src, bool convert) { + py_type py_value; + + if (!src) { + return false; + } + +#if !defined(PYPY_VERSION) + auto index_check = [](PyObject *o) { return PyIndex_Check(o); }; +#else + // In PyPy 7.3.3, `PyIndex_Check` is implemented by calling `__index__`, + // while CPython only considers the existence of `nb_index`/`__index__`. + auto index_check = [](PyObject *o) { return hasattr(o, "__index__"); }; +#endif + + if (std::is_floating_point::value) { + if (convert || PyFloat_Check(src.ptr())) { + py_value = (py_type) PyFloat_AsDouble(src.ptr()); + } else { + return false; + } + } else if (PyFloat_Check(src.ptr()) + || (!convert && !PYBIND11_LONG_CHECK(src.ptr()) && !index_check(src.ptr()))) { + return false; + } else { + handle src_or_index = src; + // PyPy: 7.3.7's 3.8 does not implement PyLong_*'s __index__ calls. +#if PY_VERSION_HEX < 0x03080000 || defined(PYPY_VERSION) + object index; + if (!PYBIND11_LONG_CHECK(src.ptr())) { // So: index_check(src.ptr()) + index = reinterpret_steal(PyNumber_Index(src.ptr())); + if (!index) { + PyErr_Clear(); + if (!convert) + return false; + } else { + src_or_index = index; + } + } +#endif + if (std::is_unsigned::value) { + py_value = as_unsigned(src_or_index.ptr()); + } else { // signed integer: + py_value = sizeof(T) <= sizeof(long) + ? (py_type) PyLong_AsLong(src_or_index.ptr()) + : (py_type) PYBIND11_LONG_AS_LONGLONG(src_or_index.ptr()); + } + } + + // Python API reported an error + bool py_err = py_value == (py_type) -1 && PyErr_Occurred(); + + // Check to see if the conversion is valid (integers should match exactly) + // Signed/unsigned checks happen elsewhere + if (py_err + || (std::is_integral::value && sizeof(py_type) != sizeof(T) + && py_value != (py_type) (T) py_value)) { + PyErr_Clear(); + if (py_err && convert && (PyNumber_Check(src.ptr()) != 0)) { + auto tmp = reinterpret_steal(std::is_floating_point::value + ? PyNumber_Float(src.ptr()) + : PyNumber_Long(src.ptr())); + PyErr_Clear(); + return load(tmp, false); + } + return false; + } + + value = (T) py_value; + return true; + } + + template + static typename std::enable_if::value, handle>::type + cast(U src, return_value_policy /* policy */, handle /* parent */) { + return PyFloat_FromDouble((double) src); + } + + template + static typename std::enable_if::value && std::is_signed::value + && (sizeof(U) <= sizeof(long)), + handle>::type + cast(U src, return_value_policy /* policy */, handle /* parent */) { + return PYBIND11_LONG_FROM_SIGNED((long) src); + } + + template + static typename std::enable_if::value && std::is_unsigned::value + && (sizeof(U) <= sizeof(unsigned long)), + handle>::type + cast(U src, return_value_policy /* policy */, handle /* parent */) { + return PYBIND11_LONG_FROM_UNSIGNED((unsigned long) src); + } + + template + static typename std::enable_if::value && std::is_signed::value + && (sizeof(U) > sizeof(long)), + handle>::type + cast(U src, return_value_policy /* policy */, handle /* parent */) { + return PyLong_FromLongLong((long long) src); + } + + template + static typename std::enable_if::value && std::is_unsigned::value + && (sizeof(U) > sizeof(unsigned long)), + handle>::type + cast(U src, return_value_policy /* policy */, handle /* parent */) { + return PyLong_FromUnsignedLongLong((unsigned long long) src); + } + + PYBIND11_TYPE_CASTER(T, const_name::value>("int", "float")); +}; + +template +struct void_caster { +public: + bool load(handle src, bool) { + if (src && src.is_none()) { + return true; + } + return false; + } + static handle cast(T, return_value_policy /* policy */, handle /* parent */) { + return none().release(); + } + PYBIND11_TYPE_CASTER(T, const_name("None")); +}; + +template <> +class type_caster : public void_caster {}; + +template <> +class type_caster : public type_caster { +public: + using type_caster::cast; + + bool load(handle h, bool) { + if (!h) { + return false; + } + if (h.is_none()) { + value = nullptr; + return true; + } + + /* Check if this is a capsule */ + if (isinstance(h)) { + value = reinterpret_borrow(h); + return true; + } + + /* Check if this is a C++ type */ + const auto &bases = all_type_info((PyTypeObject *) type::handle_of(h).ptr()); + if (bases.size() == 1) { // Only allowing loading from a single-value type + value = values_and_holders(reinterpret_cast(h.ptr())).begin()->value_ptr(); + return true; + } + + /* Fail */ + return false; + } + + static handle cast(const void *ptr, return_value_policy /* policy */, handle /* parent */) { + if (ptr) { + return capsule(ptr).release(); + } + return none().release(); + } + + template + using cast_op_type = void *&; + explicit operator void *&() { return value; } + static constexpr auto name = const_name("capsule"); + +private: + void *value = nullptr; +}; + +template <> +class type_caster : public void_caster {}; + +template <> +class type_caster { +public: + bool load(handle src, bool convert) { + if (!src) { + return false; + } + if (src.ptr() == Py_True) { + value = true; + return true; + } + if (src.ptr() == Py_False) { + value = false; + return true; + } + if (convert || is_numpy_bool(src)) { + // (allow non-implicit conversion for numpy booleans), use strncmp + // since NumPy 1.x had an additional trailing underscore. + + Py_ssize_t res = -1; + if (src.is_none()) { + res = 0; // None is implicitly converted to False + } +#if defined(PYPY_VERSION) + // On PyPy, check that "__bool__" attr exists + else if (hasattr(src, PYBIND11_BOOL_ATTR)) { + res = PyObject_IsTrue(src.ptr()); + } +#else + // Alternate approach for CPython: this does the same as the above, but optimized + // using the CPython API so as to avoid an unneeded attribute lookup. + else if (auto *tp_as_number = src.ptr()->ob_type->tp_as_number) { + if (PYBIND11_NB_BOOL(tp_as_number)) { + res = (*PYBIND11_NB_BOOL(tp_as_number))(src.ptr()); + } + } +#endif + if (res == 0 || res == 1) { + value = (res != 0); + return true; + } + PyErr_Clear(); + } + return false; + } + static handle cast(bool src, return_value_policy /* policy */, handle /* parent */) { + return handle(src ? Py_True : Py_False).inc_ref(); + } + PYBIND11_TYPE_CASTER(bool, const_name("bool")); + +private: + // Test if an object is a NumPy boolean (without fetching the type). + static inline bool is_numpy_bool(handle object) { + const char *type_name = Py_TYPE(object.ptr())->tp_name; + // Name changed to `numpy.bool` in NumPy 2, `numpy.bool_` is needed for 1.x support + return std::strcmp("numpy.bool", type_name) == 0 + || std::strcmp("numpy.bool_", type_name) == 0; + } +}; + +// Helper class for UTF-{8,16,32} C++ stl strings: +template +struct string_caster { + using CharT = typename StringType::value_type; + + // Simplify life by being able to assume standard char sizes (the standard only guarantees + // minimums, but Python requires exact sizes) + static_assert(!std::is_same::value || sizeof(CharT) == 1, + "Unsupported char size != 1"); +#if defined(PYBIND11_HAS_U8STRING) + static_assert(!std::is_same::value || sizeof(CharT) == 1, + "Unsupported char8_t size != 1"); +#endif + static_assert(!std::is_same::value || sizeof(CharT) == 2, + "Unsupported char16_t size != 2"); + static_assert(!std::is_same::value || sizeof(CharT) == 4, + "Unsupported char32_t size != 4"); + // wchar_t can be either 16 bits (Windows) or 32 (everywhere else) + static_assert(!std::is_same::value || sizeof(CharT) == 2 || sizeof(CharT) == 4, + "Unsupported wchar_t size != 2/4"); + static constexpr size_t UTF_N = 8 * sizeof(CharT); + + bool load(handle src, bool) { + handle load_src = src; + if (!src) { + return false; + } + if (!PyUnicode_Check(load_src.ptr())) { + return load_raw(load_src); + } + + // For UTF-8 we avoid the need for a temporary `bytes` object by using + // `PyUnicode_AsUTF8AndSize`. + if (UTF_N == 8) { + Py_ssize_t size = -1; + const auto *buffer + = reinterpret_cast(PyUnicode_AsUTF8AndSize(load_src.ptr(), &size)); + if (!buffer) { + PyErr_Clear(); + return false; + } + value = StringType(buffer, static_cast(size)); + return true; + } + + auto utfNbytes + = reinterpret_steal(PyUnicode_AsEncodedString(load_src.ptr(), + UTF_N == 8 ? "utf-8" + : UTF_N == 16 ? "utf-16" + : "utf-32", + nullptr)); + if (!utfNbytes) { + PyErr_Clear(); + return false; + } + + const auto *buffer + = 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 (UTF_N > 8) { + buffer++; + length--; + } + value = StringType(buffer, length); + + // If we're loading a string_view we need to keep the encoded Python object alive: + if (IsView) { + loader_life_support::add_patient(utfNbytes); + } + + return true; + } + + static handle + cast(const StringType &src, return_value_policy /* policy */, handle /* parent */) { + const char *buffer = reinterpret_cast(src.data()); + auto nbytes = ssize_t(src.size() * sizeof(CharT)); + handle s = decode_utfN(buffer, nbytes); + if (!s) { + throw error_already_set(); + } + return s; + } + + PYBIND11_TYPE_CASTER(StringType, const_name(PYBIND11_STRING_NAME)); + +private: + static handle decode_utfN(const char *buffer, ssize_t nbytes) { +#if !defined(PYPY_VERSION) + return UTF_N == 8 ? PyUnicode_DecodeUTF8(buffer, nbytes, nullptr) + : UTF_N == 16 ? PyUnicode_DecodeUTF16(buffer, nbytes, nullptr, nullptr) + : PyUnicode_DecodeUTF32(buffer, nbytes, nullptr, nullptr); +#else + // PyPy segfaults when on PyUnicode_DecodeUTF16 (and possibly on PyUnicode_DecodeUTF32 as + // well), so bypass the whole thing by just passing the encoding as a string value, which + // works properly: + return PyUnicode_Decode(buffer, + nbytes, + UTF_N == 8 ? "utf-8" + : UTF_N == 16 ? "utf-16" + : "utf-32", + nullptr); +#endif + } + + // When loading into a std::string or char*, accept a bytes/bytearray object as-is (i.e. + // without any encoding/decoding attempt). For other C++ char sizes this is a no-op. + // which supports loading a unicode from a str, doesn't take this path. + template + bool load_raw(enable_if_t::value, handle> src) { + if (PYBIND11_BYTES_CHECK(src.ptr())) { + // We were passed raw bytes; accept it into a std::string or char* + // without any encoding attempt. + const char *bytes = PYBIND11_BYTES_AS_STRING(src.ptr()); + if (!bytes) { + pybind11_fail("Unexpected PYBIND11_BYTES_AS_STRING() failure."); + } + value = StringType(bytes, (size_t) PYBIND11_BYTES_SIZE(src.ptr())); + return true; + } + if (PyByteArray_Check(src.ptr())) { + // We were passed a bytearray; accept it into a std::string or char* + // without any encoding attempt. + const char *bytearray = PyByteArray_AsString(src.ptr()); + if (!bytearray) { + pybind11_fail("Unexpected PyByteArray_AsString() failure."); + } + value = StringType(bytearray, (size_t) PyByteArray_Size(src.ptr())); + return true; + } + + return false; + } + + template + bool load_raw(enable_if_t::value, handle>) { + return false; + } +}; + +template +struct type_caster, + enable_if_t::value>> + : string_caster> {}; + +#ifdef PYBIND11_HAS_STRING_VIEW +template +struct type_caster, + enable_if_t::value>> + : string_caster, true> {}; +#endif + +// Type caster for C-style strings. We basically use a std::string type caster, but also add the +// ability to use None as a nullptr char* (which the string caster doesn't allow). +template +struct type_caster::value>> { + using StringType = std::basic_string; + using StringCaster = make_caster; + StringCaster str_caster; + bool none = false; + CharT one_char = 0; + +public: + bool load(handle src, bool convert) { + if (!src) { + return false; + } + if (src.is_none()) { + // Defer accepting None to other overloads (if we aren't in convert mode): + if (!convert) { + return false; + } + none = true; + return true; + } + return str_caster.load(src, convert); + } + + static handle cast(const CharT *src, return_value_policy policy, handle parent) { + if (src == nullptr) { + return pybind11::none().release(); + } + return StringCaster::cast(StringType(src), policy, parent); + } + + static handle cast(CharT src, return_value_policy policy, handle parent) { + if (std::is_same::value) { + handle s = PyUnicode_DecodeLatin1((const char *) &src, 1, nullptr); + if (!s) { + throw error_already_set(); + } + return s; + } + return StringCaster::cast(StringType(1, src), policy, parent); + } + + explicit operator CharT *() { + return none ? nullptr : const_cast(static_cast(str_caster).c_str()); + } + explicit operator CharT &() { + if (none) { + throw value_error("Cannot convert None to a character"); + } + + auto &value = static_cast(str_caster); + size_t str_len = value.size(); + if (str_len == 0) { + throw value_error("Cannot convert empty string to a character"); + } + + // If we're in UTF-8 mode, we have two possible failures: one for a unicode character that + // is too high, and one for multiple unicode characters (caught later), so we need to + // 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 (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 + // 0b1110xxxx - start of 3-byte sequence + // 0b11110xxx - start of 4-byte sequence + size_t char0_bytes = (v0 & 0x80) == 0 ? 1 + : (v0 & 0xE0) == 0xC0 ? 2 + : (v0 & 0xF0) == 0xE0 ? 3 + : 4; + + if (char0_bytes == str_len) { + // If we have a 128-255 value, we can decode it into a single char: + if (char0_bytes == 2 && (v0 & 0xFC) == 0xC0) { // 0x110000xx 0x10xxxxxx + one_char = static_cast(((v0 & 3) << 6) + + (static_cast(value[1]) & 0x3F)); + return one_char; + } + // Otherwise we have a single character, but it's > U+00FF + throw value_error("Character code point not in range(0x100)"); + } + } + + // 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 (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)"); + } + } + + if (str_len != 1) { + throw value_error("Expected a character, but multi-character string found"); + } + + one_char = value[0]; + return one_char; + } + + static constexpr auto name = const_name(PYBIND11_STRING_NAME); + template + using cast_op_type = pybind11::detail::cast_op_type<_T>; +}; + +// Base implementation for std::tuple and std::pair +template