Compare commits
29 Commits
master
...
release/20
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d2a2ee1b22 | ||
|
|
5b2c69628d | ||
|
|
4586ca2185 | ||
|
|
e333311b5a | ||
|
|
ef24f51a97 | ||
|
|
d1ee5b1da5 | ||
|
|
a9f8a279d4 | ||
|
|
f847d42b9e | ||
|
|
c055784352 | ||
|
|
6bf30ec3f4 | ||
|
|
d37bb3abeb | ||
|
|
3716af7a04 | ||
|
|
08ac19f242 | ||
|
|
574734680f | ||
|
|
89f14594e1 | ||
|
|
25de42037f | ||
|
|
f04ada2d44 | ||
|
|
722c3592a4 | ||
|
|
223e8c6877 | ||
|
|
5ecc7caa23 | ||
|
|
e494e6ba10 | ||
|
|
69aaab7811 | ||
|
|
157e1b0f86 | ||
|
|
25340d8ec7 | ||
|
|
88fb98db6e | ||
|
|
0031f40ec5 | ||
|
|
9696d58498 | ||
|
|
c419f8951d | ||
|
|
6f3b3f9efd |
@@ -72,7 +72,10 @@ if(fmt_FOUND)
|
||||
# OpmSatellites will not add the library, do it here.
|
||||
list(APPEND opm-common_LIBRARIES fmt::fmt)
|
||||
else()
|
||||
include(DownloadFmt)
|
||||
add_definitions(-DFMT_HEADER_ONLY)
|
||||
include_directories(SYSTEM ${PROJECT_SOURCE_DIR}/external/fmtlib/include)
|
||||
# Not sure why this is needed.
|
||||
list(APPEND EXTRA_INCLUDES ${PROJECT_SOURCE_DIR}/external/fmtlib/include)
|
||||
endif()
|
||||
|
||||
if(OPM_ENABLE_EMBEDDED_PYTHON AND NOT OPM_ENABLE_PYTHON)
|
||||
@@ -88,6 +91,10 @@ include (CMakeLists_files.cmake)
|
||||
|
||||
macro (config_hook)
|
||||
if(ENABLE_ECL_INPUT)
|
||||
if(NOT cjson_FOUND)
|
||||
list(APPEND EXTRA_INCLUDES ${PROJECT_SOURCE_DIR}/external/cjson)
|
||||
endif()
|
||||
|
||||
# For this project
|
||||
include_directories(${EXTRA_INCLUDES} ${PROJECT_BINARY_DIR}/include)
|
||||
# For downstreams
|
||||
@@ -126,12 +133,6 @@ macro (prereqs_hook)
|
||||
endmacro (prereqs_hook)
|
||||
|
||||
macro (sources_hook)
|
||||
if(NOT cjson_FOUND)
|
||||
include(DownloadCjson)
|
||||
include_directories(${cjson_SOURCE_DIR})
|
||||
list(APPEND opm-common_SOURCES ${cjson_SOURCE_DIR}/cJSON.c)
|
||||
endif()
|
||||
|
||||
if(ENABLE_ECL_INPUT)
|
||||
# Keyword generation
|
||||
include(GenerateKeywords.cmake)
|
||||
@@ -268,7 +269,10 @@ if (OPM_ENABLE_PYTHON)
|
||||
endif()
|
||||
find_package(pybind11 2.2 CONFIG)
|
||||
if (NOT pybind11_FOUND)
|
||||
include(DownloadPyBind11)
|
||||
add_subdirectory(python/pybind11)
|
||||
if (NOT pybind11_INCLUDE_DIRS)
|
||||
set(pybind11_INCLUDE_DIRS ${PYBIND11_INCLUDE_DIR} ${PYTHON_INCLUDE_DIRS})
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
|
||||
|
||||
@@ -142,7 +142,6 @@ if(ENABLE_ECL_INPUT)
|
||||
src/opm/input/eclipse/EclipseState/InitConfig/Equil.cpp
|
||||
src/opm/input/eclipse/EclipseState/InitConfig/FoamConfig.cpp
|
||||
src/opm/input/eclipse/EclipseState/InitConfig/InitConfig.cpp
|
||||
src/opm/input/eclipse/EclipseState/IOConfig/FIPConfig.cpp
|
||||
src/opm/input/eclipse/EclipseState/IOConfig/IOConfig.cpp
|
||||
src/opm/input/eclipse/EclipseState/Runspec.cpp
|
||||
src/opm/input/eclipse/EclipseState/Phase.cpp
|
||||
@@ -177,7 +176,6 @@ if(ENABLE_ECL_INPUT)
|
||||
src/opm/input/eclipse/Schedule/Group/GConSump.cpp
|
||||
src/opm/input/eclipse/Schedule/Group/GroupEconProductionLimits.cpp
|
||||
src/opm/input/eclipse/Schedule/Group/GTNode.cpp
|
||||
src/opm/input/eclipse/Schedule/HandlerContext.cpp
|
||||
src/opm/input/eclipse/Schedule/KeywordHandlers.cpp
|
||||
src/opm/input/eclipse/Schedule/MessageLimits.cpp
|
||||
src/opm/input/eclipse/Schedule/MSW/icd.cpp
|
||||
@@ -185,7 +183,6 @@ if(ENABLE_ECL_INPUT)
|
||||
src/opm/input/eclipse/Schedule/MSW/Segment.cpp
|
||||
src/opm/input/eclipse/Schedule/MSW/SegmentMatcher.cpp
|
||||
src/opm/input/eclipse/Schedule/MSW/WellSegments.cpp
|
||||
src/opm/input/eclipse/Schedule/MSW/WelSegsSet.cpp
|
||||
src/opm/input/eclipse/Schedule/MSW/AICD.cpp
|
||||
src/opm/input/eclipse/Schedule/MSW/SICD.cpp
|
||||
src/opm/input/eclipse/Schedule/MSW/Valve.cpp
|
||||
@@ -231,7 +228,6 @@ if(ENABLE_ECL_INPUT)
|
||||
src/opm/input/eclipse/Schedule/Well/WINJMULT.cpp
|
||||
src/opm/input/eclipse/Schedule/Well/WList.cpp
|
||||
src/opm/input/eclipse/Schedule/Well/WListManager.cpp
|
||||
src/opm/input/eclipse/Schedule/Well/WDFAC.cpp
|
||||
src/opm/input/eclipse/Schedule/Well/WVFPDP.cpp
|
||||
src/opm/input/eclipse/Schedule/Well/WVFPEXP.cpp
|
||||
src/opm/input/eclipse/Schedule/WellTraj/RigEclipseWellLogExtractor.cpp
|
||||
@@ -265,22 +261,21 @@ if(ENABLE_ECL_INPUT)
|
||||
src/opm/input/eclipse/EclipseState/Tables/BrineDensityTable.cpp
|
||||
src/opm/input/eclipse/EclipseState/Tables/SolventDensityTable.cpp
|
||||
src/opm/input/eclipse/EclipseState/Tables/Tabdims.cpp
|
||||
src/opm/input/eclipse/Schedule/UDQ/UDQActive.cpp
|
||||
src/opm/input/eclipse/Schedule/UDQ/UDQAssign.cpp
|
||||
src/opm/input/eclipse/Schedule/UDQ/UDQASTNode.cpp
|
||||
src/opm/input/eclipse/Schedule/UDQ/UDQConfig.cpp
|
||||
src/opm/input/eclipse/Schedule/UDQ/UDQContext.cpp
|
||||
src/opm/input/eclipse/Schedule/UDQ/UDQDefine.cpp
|
||||
src/opm/input/eclipse/Schedule/UDQ/UDQEnums.cpp
|
||||
src/opm/input/eclipse/Schedule/UDQ/UDQFunction.cpp
|
||||
src/opm/input/eclipse/Schedule/UDQ/UDQFunctionTable.cpp
|
||||
src/opm/input/eclipse/Schedule/UDQ/UDQInput.cpp
|
||||
src/opm/input/eclipse/Schedule/UDQ/UDQParams.cpp
|
||||
src/opm/input/eclipse/Schedule/UDQ/UDQParser.cpp
|
||||
src/opm/input/eclipse/Schedule/UDQ/UDQSet.cpp
|
||||
src/opm/input/eclipse/Schedule/UDQ/UDQState.cpp
|
||||
src/opm/input/eclipse/Schedule/UDQ/UDQActive.cpp
|
||||
src/opm/input/eclipse/Schedule/UDQ/UDQAssign.cpp
|
||||
src/opm/input/eclipse/Schedule/UDQ/UDQDefine.cpp
|
||||
src/opm/input/eclipse/Schedule/UDQ/UDQEnums.cpp
|
||||
src/opm/input/eclipse/Schedule/UDQ/UDQToken.cpp
|
||||
src/opm/input/eclipse/Schedule/UDQ/UDT.cpp
|
||||
src/opm/input/eclipse/Schedule/UDQ/UDQConfig.cpp
|
||||
src/opm/input/eclipse/Schedule/UDQ/UDQContext.cpp
|
||||
src/opm/input/eclipse/Schedule/UDQ/UDQFunction.cpp
|
||||
src/opm/input/eclipse/Schedule/UDQ/UDQFunctionTable.cpp
|
||||
src/opm/input/eclipse/Schedule/UDQ/UDQInput.cpp
|
||||
src/opm/input/eclipse/Schedule/UDQ/UDQState.cpp
|
||||
src/opm/input/eclipse/Schedule/VFPInjTable.cpp
|
||||
src/opm/input/eclipse/Schedule/VFPProdTable.cpp
|
||||
src/opm/input/eclipse/Parser/ErrorGuard.cpp
|
||||
@@ -356,6 +351,10 @@ if(ENABLE_ECL_INPUT)
|
||||
list( APPEND PYTHON_CXX_DEPENDS ${PYTHON_CXX_SOURCE_FILES}
|
||||
python/cxx/converters.hpp
|
||||
python/cxx/export.hpp)
|
||||
|
||||
if(NOT cjson_FOUND)
|
||||
list(APPEND MAIN_SOURCE_FILES external/cjson/cJSON.c)
|
||||
endif()
|
||||
endif()
|
||||
if(ENABLE_ECL_OUTPUT)
|
||||
list( APPEND MAIN_SOURCE_FILES
|
||||
@@ -464,7 +463,6 @@ if(ENABLE_ECL_INPUT)
|
||||
tests/rst_test.cpp
|
||||
tests/test_ActiveGridCells.cpp
|
||||
tests/test_CopyablePtr.cpp
|
||||
tests/test_CSRGraphFromCoordinates.cpp
|
||||
tests/test_ERsm.cpp
|
||||
tests/test_GuideRate.cpp
|
||||
tests/test_RestartFileView.cpp
|
||||
@@ -502,7 +500,6 @@ if(ENABLE_ECL_INPUT)
|
||||
tests/parser/FaceDirTests.cpp
|
||||
tests/parser/FaultTests.cpp
|
||||
tests/parser/FieldPropsTests.cpp
|
||||
tests/parser/FIPConfigTests.cpp
|
||||
tests/parser/FoamTests.cpp
|
||||
tests/parser/FunctionalTests.cpp
|
||||
tests/parser/GeomodifierTests.cpp
|
||||
@@ -550,7 +547,6 @@ if(ENABLE_ECL_INPUT)
|
||||
tests/parser/TransMultTests.cpp
|
||||
tests/parser/TuningTests.cpp
|
||||
tests/parser/UDQTests.cpp
|
||||
tests/parser/UDTTests.cpp
|
||||
tests/parser/UnitTests.cpp
|
||||
tests/parser/integration/NNCTests.cpp
|
||||
tests/parser/WellSolventTests.cpp
|
||||
@@ -760,8 +756,6 @@ list( APPEND PUBLIC_HEADER_FILES
|
||||
opm/common/OpmLog/StreamLog.hpp
|
||||
opm/common/OpmLog/TimerLog.hpp
|
||||
opm/common/utility/ActiveGridCells.hpp
|
||||
opm/common/utility/CSRGraphFromCoordinates.hpp
|
||||
opm/common/utility/CSRGraphFromCoordinates_impl.hpp
|
||||
opm/common/utility/Demangle.hpp
|
||||
opm/common/utility/FileSystem.hpp
|
||||
opm/common/utility/OpmInputError.hpp
|
||||
@@ -1260,7 +1254,6 @@ if(ENABLE_ECL_INPUT)
|
||||
opm/input/eclipse/Schedule/Well/WellPolymerProperties.hpp
|
||||
opm/input/eclipse/Schedule/Well/WellTracerProperties.hpp
|
||||
opm/input/eclipse/Schedule/Well/WINJMULT.hpp
|
||||
opm/input/eclipse/Schedule/Well/WDFAC.hpp
|
||||
opm/input/eclipse/Schedule/Well/WVFPDP.hpp
|
||||
opm/input/eclipse/Schedule/Well/WVFPEXP.hpp
|
||||
opm/input/eclipse/Schedule/Well/WellTestConfig.hpp
|
||||
@@ -1302,26 +1295,24 @@ if(ENABLE_ECL_INPUT)
|
||||
opm/input/eclipse/EclipseState/SimulationConfig/RockConfig.hpp
|
||||
opm/input/eclipse/EclipseState/SimulationConfig/SimulationConfig.hpp
|
||||
opm/input/eclipse/Schedule/MSW/Valve.hpp
|
||||
opm/input/eclipse/EclipseState/IOConfig/FIPConfig.hpp
|
||||
opm/input/eclipse/EclipseState/IOConfig/IOConfig.hpp
|
||||
opm/input/eclipse/EclipseState/checkDeck.hpp
|
||||
opm/input/eclipse/EclipseState/Phase.hpp
|
||||
opm/input/eclipse/EclipseState/Runspec.hpp
|
||||
opm/input/eclipse/Schedule/UDQ/UDQActive.hpp
|
||||
opm/input/eclipse/Schedule/UDQ/UDQAssign.hpp
|
||||
opm/input/eclipse/Schedule/UDQ/UDQASTNode.hpp
|
||||
opm/input/eclipse/Schedule/UDQ/UDQConfig.hpp
|
||||
opm/input/eclipse/Schedule/UDQ/UDQContext.hpp
|
||||
opm/input/eclipse/Schedule/UDQ/UDQDefine.hpp
|
||||
opm/input/eclipse/Schedule/UDQ/UDQContext.hpp
|
||||
opm/input/eclipse/Schedule/UDQ/UDQState.hpp
|
||||
opm/input/eclipse/Schedule/UDQ/UDQConfig.hpp
|
||||
opm/input/eclipse/Schedule/UDQ/UDQEnums.hpp
|
||||
opm/input/eclipse/Schedule/UDQ/UDQParams.hpp
|
||||
opm/input/eclipse/Schedule/UDQ/UDQInput.hpp
|
||||
opm/input/eclipse/Schedule/UDQ/UDQActive.hpp
|
||||
opm/input/eclipse/Schedule/UDQ/UDQSet.hpp
|
||||
opm/input/eclipse/Schedule/UDQ/UDQToken.hpp
|
||||
opm/input/eclipse/Schedule/UDQ/UDQFunction.hpp
|
||||
opm/input/eclipse/Schedule/UDQ/UDQFunctionTable.hpp
|
||||
opm/input/eclipse/Schedule/UDQ/UDQInput.hpp
|
||||
opm/input/eclipse/Schedule/UDQ/UDQParams.hpp
|
||||
opm/input/eclipse/Schedule/UDQ/UDQSet.hpp
|
||||
opm/input/eclipse/Schedule/UDQ/UDQState.hpp
|
||||
opm/input/eclipse/Schedule/UDQ/UDQToken.hpp
|
||||
opm/input/eclipse/Schedule/UDQ/UDT.hpp
|
||||
opm/input/eclipse/Deck/DeckItem.hpp
|
||||
opm/input/eclipse/Deck/Deck.hpp
|
||||
opm/input/eclipse/Deck/DeckView.hpp
|
||||
|
||||
@@ -33,7 +33,7 @@ set(genkw_SOURCES src/opm/json/JsonObject.cpp
|
||||
src/opm/common/OpmLog/LogUtil.cpp
|
||||
)
|
||||
if(NOT cjson_FOUND)
|
||||
list(APPEND genkw_SOURCES ${cjson_SOURCE_DIR}/cJSON.c)
|
||||
list(APPEND genkw_SOURCES external/cjson/cJSON.c)
|
||||
endif()
|
||||
add_executable(genkw ${genkw_SOURCES})
|
||||
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
include(FetchContent)
|
||||
FetchContent_Declare(cjson
|
||||
DOWNLOAD_EXTRACT_TIMESTAMP ON
|
||||
URL https://github.com/DaveGamble/cJSON/archive/refs/tags/v1.7.16.tar.gz
|
||||
URL_HASH SHA512=3a894de03c33d89f1e7ee572418d5483c844d38e1e64aa4f6297ddaa01f4111f07601f8d26617b424b5af15d469e3955dae075d9f30b5c25e16ec348fdb06e6f)
|
||||
FetchContent_Populate(cjson)
|
||||
|
||||
# set(ENABLE_CJSON_TEST OFF CACHE BOOL "")
|
||||
# set(BUILD_SHARED_AND_STATIC_LIBS OFF CACHE BOOL "")
|
||||
# set(CJSON_BUILD_SHARED_LIBS OFF CACHE BOOL "")
|
||||
# set(CJSON_OVERRIDE_BUILD_SHARED_LIBS ON CACHE BOOL "")
|
||||
|
||||
# add_subdirectory(${cjson_SOURCE_DIR} ${cjson_BINARY_DIR})
|
||||
|
||||
# add_library(cjson::cjson STATIC IMPORTED)
|
||||
# set_target_properties(cjson::cjson PROPERTIES
|
||||
# INTERFACE_INCLUDE_DIRECTORIES ${cjson_SOURCE_DIR}
|
||||
# IMPORTED_LOCATION lib/libcjson.a)
|
||||
@@ -1,17 +0,0 @@
|
||||
include(FetchContent)
|
||||
|
||||
if(NOT fmt_POPULATED)
|
||||
FetchContent_Declare(fmt
|
||||
DOWNLOAD_EXTRACT_TIMESTAMP ON
|
||||
URL https://github.com/fmtlib/fmt/archive/refs/tags/10.1.1.tar.gz
|
||||
URL_HASH SHA512=288c349baac5f96f527d5b1bed0fa5f031aa509b4526560c684281388e91909a280c3262a2474d963b5d1bf7064b1c9930c6677fe54a0d8f86982d063296a54c)
|
||||
FetchContent_Populate(fmt)
|
||||
endif()
|
||||
|
||||
# We do not want to use the target directly as that means it ends up
|
||||
# in our depends list for downstream modules and the installation list.
|
||||
# Instead, we just download and use header only mode.
|
||||
add_compile_definitions(FMT_HEADER_ONLY)
|
||||
include_directories(${fmt_SOURCE_DIR}/include)
|
||||
set(fmt_POPULATED ${fmt_POPULATED} PARENT_SCOPE)
|
||||
set(fmt_SOURCE_DIR ${fmt_SOURCE_DIR} PARENT_SCOPE)
|
||||
@@ -1,6 +0,0 @@
|
||||
include(FetchContent)
|
||||
FetchContent_Declare(pybind11
|
||||
DOWNLOAD_EXTRACT_TIMESTAMP ON
|
||||
URL https://github.com/pybind/pybind11/archive/refs/tags/v2.11.1.tar.gz
|
||||
URL_HASH SHA512=ed1512ff0bca3bc0a45edc2eb8c77f8286ab9389f6ff1d5cb309be24bc608abbe0df6a7f5cb18c8f80a3bfa509058547c13551c3cd6a759af708fd0cdcdd9e95)
|
||||
FetchContent_MakeAvailable(pybind11)
|
||||
@@ -18,7 +18,7 @@ if(USE_DAMARIS_LIB AND MPI_FOUND)
|
||||
message(STATUS "The Damaris library does NOT have Catalyst support")
|
||||
endif()
|
||||
else()
|
||||
message(FATAL_ERROR "The Damaris library was requested but NOT found")
|
||||
message(STATUS "The Damaris library was requested but NOT found")
|
||||
endif()
|
||||
else() # User did not request Damaris support
|
||||
unset(HAVE_DAMARIS)
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
|
||||
Module: opm-common
|
||||
Description: Open Porous Media Initiative shared infrastructure
|
||||
Version: 2024.04-pre
|
||||
Label: 2024.04-pre
|
||||
Version: 2023.10
|
||||
Label: 2023.10
|
||||
Maintainer: opm@opm-project.org
|
||||
MaintainerName: OPM community
|
||||
Url: http://opm-project.org
|
||||
|
||||
247
external/cjson/README
vendored
Normal file
247
external/cjson/README
vendored
Normal file
@@ -0,0 +1,247 @@
|
||||
/*
|
||||
Copyright (c) 2009 Dave Gamble
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
*/
|
||||
|
||||
Welcome to cJSON.
|
||||
|
||||
cJSON aims to be the dumbest possible parser that you can get your job done with.
|
||||
It's a single file of C, and a single header file.
|
||||
|
||||
JSON is described best here: http://www.json.org/
|
||||
It's like XML, but fat-free. You use it to move data around, store things, or just
|
||||
generally represent your program's state.
|
||||
|
||||
|
||||
First up, how do I build?
|
||||
Add cJSON.c to your project, and put cJSON.h somewhere in the header search path.
|
||||
For example, to build the test app:
|
||||
|
||||
gcc cJSON.c test.c -o test -lm
|
||||
./test
|
||||
|
||||
|
||||
As a library, cJSON exists to take away as much legwork as it can, but not get in your way.
|
||||
As a point of pragmatism (i.e. ignoring the truth), I'm going to say that you can use it
|
||||
in one of two modes: Auto and Manual. Let's have a quick run-through.
|
||||
|
||||
|
||||
I lifted some JSON from this page: http://www.json.org/fatfree.html
|
||||
That page inspired me to write cJSON, which is a parser that tries to share the same
|
||||
philosophy as JSON itself. Simple, dumb, out of the way.
|
||||
|
||||
Some JSON:
|
||||
{
|
||||
"name": "Jack (\"Bee\") Nimble",
|
||||
"format": {
|
||||
"type": "rect",
|
||||
"width": 1920,
|
||||
"height": 1080,
|
||||
"interlace": false,
|
||||
"frame rate": 24
|
||||
}
|
||||
}
|
||||
|
||||
Assume that you got this from a file, a webserver, or magic JSON elves, whatever,
|
||||
you have a char * to it. Everything is a cJSON struct.
|
||||
Get it parsed:
|
||||
cJSON *root = cJSON_Parse(my_json_string);
|
||||
|
||||
This is an object. We're in C. We don't have objects. But we do have structs.
|
||||
What's the framerate?
|
||||
|
||||
cJSON *format = cJSON_GetObjectItem(root,"format");
|
||||
int framerate = cJSON_GetObjectItem(format,"frame rate")->valueint;
|
||||
|
||||
|
||||
Want to change the framerate?
|
||||
cJSON_GetObjectItem(format,"frame rate")->valueint=25;
|
||||
|
||||
Back to disk?
|
||||
char *rendered=cJSON_Print(root);
|
||||
|
||||
Finished? Delete the root (this takes care of everything else).
|
||||
cJSON_Delete(root);
|
||||
|
||||
That's AUTO mode. If you're going to use Auto mode, you really ought to check pointers
|
||||
before you dereference them. If you want to see how you'd build this struct in code?
|
||||
cJSON *root,*fmt;
|
||||
root=cJSON_CreateObject();
|
||||
cJSON_AddItemToObject(root, "name", cJSON_CreateString("Jack (\"Bee\") Nimble"));
|
||||
cJSON_AddItemToObject(root, "format", fmt=cJSON_CreateObject());
|
||||
cJSON_AddStringToObject(fmt,"type", "rect");
|
||||
cJSON_AddNumberToObject(fmt,"width", 1920);
|
||||
cJSON_AddNumberToObject(fmt,"height", 1080);
|
||||
cJSON_AddFalseToObject (fmt,"interlace");
|
||||
cJSON_AddNumberToObject(fmt,"frame rate", 24);
|
||||
|
||||
Hopefully we can agree that's not a lot of code? There's no overhead, no unnecessary setup.
|
||||
Look at test.c for a bunch of nice examples, mostly all ripped off the json.org site, and
|
||||
a few from elsewhere.
|
||||
|
||||
What about manual mode? First up you need some detail.
|
||||
Let's cover how the cJSON objects represent the JSON data.
|
||||
cJSON doesn't distinguish arrays from objects in handling; just type.
|
||||
Each cJSON has, potentially, a child, siblings, value, a name.
|
||||
|
||||
The root object has: Object Type and a Child
|
||||
The Child has name "name", with value "Jack ("Bee") Nimble", and a sibling:
|
||||
Sibling has type Object, name "format", and a child.
|
||||
That child has type String, name "type", value "rect", and a sibling:
|
||||
Sibling has type Number, name "width", value 1920, and a sibling:
|
||||
Sibling has type Number, name "height", value 1080, and a sibling:
|
||||
Sibling hs type False, name "interlace", and a sibling:
|
||||
Sibling has type Number, name "frame rate", value 24
|
||||
|
||||
Here's the structure:
|
||||
typedef struct cJSON {
|
||||
struct cJSON *next,*prev;
|
||||
struct cJSON *child;
|
||||
|
||||
int type;
|
||||
|
||||
char *valuestring;
|
||||
int valueint;
|
||||
double valuedouble;
|
||||
|
||||
char *string;
|
||||
} cJSON;
|
||||
|
||||
By default all values are 0 unless set by virtue of being meaningful.
|
||||
|
||||
next/prev is a doubly linked list of siblings. next takes you to your sibling,
|
||||
prev takes you back from your sibling to you.
|
||||
Only objects and arrays have a "child", and it's the head of the doubly linked list.
|
||||
A "child" entry will have prev==0, but next potentially points on. The last sibling has next=0.
|
||||
The type expresses Null/True/False/Number/String/Array/Object, all of which are #defined in
|
||||
cJSON.h
|
||||
|
||||
A Number has valueint and valuedouble. If you're expecting an int, read valueint, if not read
|
||||
valuedouble.
|
||||
|
||||
Any entry which is in the linked list which is the child of an object will have a "string"
|
||||
which is the "name" of the entry. When I said "name" in the above example, that's "string".
|
||||
"string" is the JSON name for the 'variable name' if you will.
|
||||
|
||||
Now you can trivially walk the lists, recursively, and parse as you please.
|
||||
You can invoke cJSON_Parse to get cJSON to parse for you, and then you can take
|
||||
the root object, and traverse the structure (which is, formally, an N-tree),
|
||||
and tokenise as you please. If you wanted to build a callback style parser, this is how
|
||||
you'd do it (just an example, since these things are very specific):
|
||||
|
||||
void parse_and_callback(cJSON *item,const char *prefix)
|
||||
{
|
||||
while (item)
|
||||
{
|
||||
char *newprefix=malloc(strlen(prefix)+strlen(item->name)+2);
|
||||
sprintf(newprefix,"%s/%s",prefix,item->name);
|
||||
int dorecurse=callback(newprefix, item->type, item);
|
||||
if (item->child && dorecurse) parse_and_callback(item->child,newprefix);
|
||||
item=item->next;
|
||||
free(newprefix);
|
||||
}
|
||||
}
|
||||
|
||||
The prefix process will build you a separated list, to simplify your callback handling.
|
||||
The 'dorecurse' flag would let the callback decide to handle sub-arrays on it's own, or
|
||||
let you invoke it per-item. For the item above, your callback might look like this:
|
||||
|
||||
int callback(const char *name,int type,cJSON *item)
|
||||
{
|
||||
if (!strcmp(name,"name")) { /* populate name */ }
|
||||
else if (!strcmp(name,"format/type") { /* handle "rect" */ }
|
||||
else if (!strcmp(name,"format/width") { /* 800 */ }
|
||||
else if (!strcmp(name,"format/height") { /* 600 */ }
|
||||
else if (!strcmp(name,"format/interlace") { /* false */ }
|
||||
else if (!strcmp(name,"format/frame rate") { /* 24 */ }
|
||||
return 1;
|
||||
}
|
||||
|
||||
Alternatively, you might like to parse iteratively.
|
||||
You'd use:
|
||||
|
||||
void parse_object(cJSON *item)
|
||||
{
|
||||
int i; for (i=0;i<cJSON_GetArraySize(item);i++)
|
||||
{
|
||||
cJSON *subitem=cJSON_GetArrayItem(item,i);
|
||||
// handle subitem.
|
||||
}
|
||||
}
|
||||
|
||||
Or, for PROPER manual mode:
|
||||
|
||||
void parse_object(cJSON *item)
|
||||
{
|
||||
cJSON *subitem=item->child;
|
||||
while (subitem)
|
||||
{
|
||||
// handle subitem
|
||||
if (subitem->child) parse_object(subitem->child);
|
||||
|
||||
subitem=subitem->next;
|
||||
}
|
||||
}
|
||||
|
||||
Of course, this should look familiar, since this is just a stripped-down version
|
||||
of the callback-parser.
|
||||
|
||||
This should cover most uses you'll find for parsing. The rest should be possible
|
||||
to infer.. and if in doubt, read the source! There's not a lot of it! ;)
|
||||
|
||||
|
||||
In terms of constructing JSON data, the example code above is the right way to do it.
|
||||
You can, of course, hand your sub-objects to other functions to populate.
|
||||
Also, if you find a use for it, you can manually build the objects.
|
||||
For instance, suppose you wanted to build an array of objects?
|
||||
|
||||
cJSON *objects[24];
|
||||
|
||||
cJSON *Create_array_of_anything(cJSON **items,int num)
|
||||
{
|
||||
int i;cJSON *prev, *root=cJSON_CreateArray();
|
||||
for (i=0;i<24;i++)
|
||||
{
|
||||
if (!i) root->child=objects[i];
|
||||
else prev->next=objects[i], objects[i]->prev=prev;
|
||||
prev=objects[i];
|
||||
}
|
||||
return root;
|
||||
}
|
||||
|
||||
and simply: Create_array_of_anything(objects,24);
|
||||
|
||||
cJSON doesn't make any assumptions about what order you create things in.
|
||||
You can attach the objects, as above, and later add children to each
|
||||
of those objects.
|
||||
|
||||
As soon as you call cJSON_Print, it renders the structure to text.
|
||||
|
||||
|
||||
|
||||
The test.c code shows how to handle a bunch of typical cases. If you uncomment
|
||||
the code, it'll load, parse and print a bunch of test files, also from json.org,
|
||||
which are more complex than I'd care to try and stash into a const char array[].
|
||||
|
||||
|
||||
Enjoy cJSON!
|
||||
|
||||
|
||||
- Dave Gamble, Aug 2009
|
||||
2
external/cjson/README.opm
vendored
Normal file
2
external/cjson/README.opm
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
This directory contains the the 1.7.10 version of the cJSON package from https://github.com/DaveGamble/cJSON
|
||||
|
||||
2936
external/cjson/cJSON.c
vendored
Normal file
2936
external/cjson/cJSON.c
vendored
Normal file
File diff suppressed because it is too large
Load Diff
285
external/cjson/cJSON.h
vendored
Normal file
285
external/cjson/cJSON.h
vendored
Normal file
@@ -0,0 +1,285 @@
|
||||
/*
|
||||
Copyright (c) 2009-2017 Dave Gamble and cJSON contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef cJSON__h
|
||||
#define cJSON__h
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C"
|
||||
{
|
||||
#endif
|
||||
|
||||
#if !defined(__WINDOWS__) && (defined(WIN32) || defined(WIN64) || defined(_MSC_VER) || defined(_WIN32))
|
||||
#define __WINDOWS__
|
||||
#endif
|
||||
|
||||
#ifdef __WINDOWS__
|
||||
|
||||
/* When compiling for windows, we specify a specific calling convention to avoid issues where we are being called from a project with a different default calling convention. For windows you have 3 define options:
|
||||
|
||||
CJSON_HIDE_SYMBOLS - Define this in the case where you don't want to ever dllexport symbols
|
||||
CJSON_EXPORT_SYMBOLS - Define this on library build when you want to dllexport symbols (default)
|
||||
CJSON_IMPORT_SYMBOLS - Define this if you want to dllimport symbol
|
||||
|
||||
For *nix builds that support visibility attribute, you can define similar behavior by
|
||||
|
||||
setting default visibility to hidden by adding
|
||||
-fvisibility=hidden (for gcc)
|
||||
or
|
||||
-xldscope=hidden (for sun cc)
|
||||
to CFLAGS
|
||||
|
||||
then using the CJSON_API_VISIBILITY flag to "export" the same symbols the way CJSON_EXPORT_SYMBOLS does
|
||||
|
||||
*/
|
||||
|
||||
#define CJSON_CDECL __cdecl
|
||||
#define CJSON_STDCALL __stdcall
|
||||
|
||||
/* export symbols by default, this is necessary for copy pasting the C and header file */
|
||||
#if !defined(CJSON_HIDE_SYMBOLS) && !defined(CJSON_IMPORT_SYMBOLS) && !defined(CJSON_EXPORT_SYMBOLS)
|
||||
#define CJSON_EXPORT_SYMBOLS
|
||||
#endif
|
||||
|
||||
#if defined(CJSON_HIDE_SYMBOLS)
|
||||
#define CJSON_PUBLIC(type) type CJSON_STDCALL
|
||||
#elif defined(CJSON_EXPORT_SYMBOLS)
|
||||
#define CJSON_PUBLIC(type) __declspec(dllexport) type CJSON_STDCALL
|
||||
#elif defined(CJSON_IMPORT_SYMBOLS)
|
||||
#define CJSON_PUBLIC(type) __declspec(dllimport) type CJSON_STDCALL
|
||||
#endif
|
||||
#else /* !__WINDOWS__ */
|
||||
#define CJSON_CDECL
|
||||
#define CJSON_STDCALL
|
||||
|
||||
#if (defined(__GNUC__) || defined(__SUNPRO_CC) || defined (__SUNPRO_C)) && defined(CJSON_API_VISIBILITY)
|
||||
#define CJSON_PUBLIC(type) __attribute__((visibility("default"))) type
|
||||
#else
|
||||
#define CJSON_PUBLIC(type) type
|
||||
#endif
|
||||
#endif
|
||||
|
||||
/* project version */
|
||||
#define CJSON_VERSION_MAJOR 1
|
||||
#define CJSON_VERSION_MINOR 7
|
||||
#define CJSON_VERSION_PATCH 10
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
/* cJSON Types: */
|
||||
#define cJSON_Invalid (0)
|
||||
#define cJSON_False (1 << 0)
|
||||
#define cJSON_True (1 << 1)
|
||||
#define cJSON_NULL (1 << 2)
|
||||
#define cJSON_Number (1 << 3)
|
||||
#define cJSON_String (1 << 4)
|
||||
#define cJSON_Array (1 << 5)
|
||||
#define cJSON_Object (1 << 6)
|
||||
#define cJSON_Raw (1 << 7) /* raw json */
|
||||
|
||||
#define cJSON_IsReference 256
|
||||
#define cJSON_StringIsConst 512
|
||||
|
||||
/* The cJSON structure: */
|
||||
typedef struct cJSON
|
||||
{
|
||||
/* next/prev allow you to walk array/object chains. Alternatively, use GetArraySize/GetArrayItem/GetObjectItem */
|
||||
struct cJSON *next;
|
||||
struct cJSON *prev;
|
||||
/* An array or object item will have a child pointer pointing to a chain of the items in the array/object. */
|
||||
struct cJSON *child;
|
||||
|
||||
/* The type of the item, as above. */
|
||||
int type;
|
||||
|
||||
/* The item's string, if type==cJSON_String and type == cJSON_Raw */
|
||||
char *valuestring;
|
||||
/* writing to valueint is DEPRECATED, use cJSON_SetNumberValue instead */
|
||||
int valueint;
|
||||
/* The item's number, if type==cJSON_Number */
|
||||
double valuedouble;
|
||||
|
||||
/* The item's name string, if this item is the child of, or is in the list of subitems of an object. */
|
||||
char *string;
|
||||
} cJSON;
|
||||
|
||||
typedef struct cJSON_Hooks
|
||||
{
|
||||
/* malloc/free are CDECL on Windows regardless of the default calling convention of the compiler, so ensure the hooks allow passing those functions directly. */
|
||||
void *(CJSON_CDECL *malloc_fn)(size_t sz);
|
||||
void (CJSON_CDECL *free_fn)(void *ptr);
|
||||
} cJSON_Hooks;
|
||||
|
||||
typedef int cJSON_bool;
|
||||
|
||||
/* Limits how deeply nested arrays/objects can be before cJSON rejects to parse them.
|
||||
* This is to prevent stack overflows. */
|
||||
#ifndef CJSON_NESTING_LIMIT
|
||||
#define CJSON_NESTING_LIMIT 1000
|
||||
#endif
|
||||
|
||||
/* returns the version of cJSON as a string */
|
||||
CJSON_PUBLIC(const char*) cJSON_Version(void);
|
||||
|
||||
/* Supply malloc, realloc and free functions to cJSON */
|
||||
CJSON_PUBLIC(void) cJSON_InitHooks(cJSON_Hooks* hooks);
|
||||
|
||||
/* Memory Management: the caller is always responsible to free the results from all variants of cJSON_Parse (with cJSON_Delete) and cJSON_Print (with stdlib free, cJSON_Hooks.free_fn, or cJSON_free as appropriate). The exception is cJSON_PrintPreallocated, where the caller has full responsibility of the buffer. */
|
||||
/* Supply a block of JSON, and this returns a cJSON object you can interrogate. */
|
||||
CJSON_PUBLIC(cJSON *) cJSON_Parse(const char *value);
|
||||
/* ParseWithOpts allows you to require (and check) that the JSON is null terminated, and to retrieve the pointer to the final byte parsed. */
|
||||
/* If you supply a ptr in return_parse_end and parsing fails, then return_parse_end will contain a pointer to the error so will match cJSON_GetErrorPtr(). */
|
||||
CJSON_PUBLIC(cJSON *) cJSON_ParseWithOpts(const char *value, const char **return_parse_end, cJSON_bool require_null_terminated);
|
||||
|
||||
/* Render a cJSON entity to text for transfer/storage. */
|
||||
CJSON_PUBLIC(char *) cJSON_Print(const cJSON *item);
|
||||
/* Render a cJSON entity to text for transfer/storage without any formatting. */
|
||||
CJSON_PUBLIC(char *) cJSON_PrintUnformatted(const cJSON *item);
|
||||
/* Render a cJSON entity to text using a buffered strategy. prebuffer is a guess at the final size. guessing well reduces reallocation. fmt=0 gives unformatted, =1 gives formatted */
|
||||
CJSON_PUBLIC(char *) cJSON_PrintBuffered(const cJSON *item, int prebuffer, cJSON_bool fmt);
|
||||
/* Render a cJSON entity to text using a buffer already allocated in memory with given length. Returns 1 on success and 0 on failure. */
|
||||
/* NOTE: cJSON is not always 100% accurate in estimating how much memory it will use, so to be safe allocate 5 bytes more than you actually need */
|
||||
CJSON_PUBLIC(cJSON_bool) cJSON_PrintPreallocated(cJSON *item, char *buffer, const int length, const cJSON_bool format);
|
||||
/* Delete a cJSON entity and all subentities. */
|
||||
CJSON_PUBLIC(void) cJSON_Delete(cJSON *c);
|
||||
|
||||
/* Returns the number of items in an array (or object). */
|
||||
CJSON_PUBLIC(int) cJSON_GetArraySize(const cJSON *array);
|
||||
/* Retrieve item number "index" from array "array". Returns NULL if unsuccessful. */
|
||||
CJSON_PUBLIC(cJSON *) cJSON_GetArrayItem(const cJSON *array, int index);
|
||||
/* Get item "string" from object. Case insensitive. */
|
||||
CJSON_PUBLIC(cJSON *) cJSON_GetObjectItem(const cJSON * const object, const char * const string);
|
||||
CJSON_PUBLIC(cJSON *) cJSON_GetObjectItemCaseSensitive(const cJSON * const object, const char * const string);
|
||||
CJSON_PUBLIC(cJSON_bool) cJSON_HasObjectItem(const cJSON *object, const char *string);
|
||||
/* For analysing failed parses. This returns a pointer to the parse error. You'll probably need to look a few chars back to make sense of it. Defined when cJSON_Parse() returns 0. 0 when cJSON_Parse() succeeds. */
|
||||
CJSON_PUBLIC(const char *) cJSON_GetErrorPtr(void);
|
||||
|
||||
/* Check if the item is a string and return its valuestring */
|
||||
CJSON_PUBLIC(char *) cJSON_GetStringValue(cJSON *item);
|
||||
|
||||
/* These functions check the type of an item */
|
||||
CJSON_PUBLIC(cJSON_bool) cJSON_IsInvalid(const cJSON * const item);
|
||||
CJSON_PUBLIC(cJSON_bool) cJSON_IsFalse(const cJSON * const item);
|
||||
CJSON_PUBLIC(cJSON_bool) cJSON_IsTrue(const cJSON * const item);
|
||||
CJSON_PUBLIC(cJSON_bool) cJSON_IsBool(const cJSON * const item);
|
||||
CJSON_PUBLIC(cJSON_bool) cJSON_IsNull(const cJSON * const item);
|
||||
CJSON_PUBLIC(cJSON_bool) cJSON_IsNumber(const cJSON * const item);
|
||||
CJSON_PUBLIC(cJSON_bool) cJSON_IsString(const cJSON * const item);
|
||||
CJSON_PUBLIC(cJSON_bool) cJSON_IsArray(const cJSON * const item);
|
||||
CJSON_PUBLIC(cJSON_bool) cJSON_IsObject(const cJSON * const item);
|
||||
CJSON_PUBLIC(cJSON_bool) cJSON_IsRaw(const cJSON * const item);
|
||||
|
||||
/* These calls create a cJSON item of the appropriate type. */
|
||||
CJSON_PUBLIC(cJSON *) cJSON_CreateNull(void);
|
||||
CJSON_PUBLIC(cJSON *) cJSON_CreateTrue(void);
|
||||
CJSON_PUBLIC(cJSON *) cJSON_CreateFalse(void);
|
||||
CJSON_PUBLIC(cJSON *) cJSON_CreateBool(cJSON_bool boolean);
|
||||
CJSON_PUBLIC(cJSON *) cJSON_CreateNumber(double num);
|
||||
CJSON_PUBLIC(cJSON *) cJSON_CreateString(const char *string);
|
||||
/* raw json */
|
||||
CJSON_PUBLIC(cJSON *) cJSON_CreateRaw(const char *raw);
|
||||
CJSON_PUBLIC(cJSON *) cJSON_CreateArray(void);
|
||||
CJSON_PUBLIC(cJSON *) cJSON_CreateObject(void);
|
||||
|
||||
/* Create a string where valuestring references a string so
|
||||
* it will not be freed by cJSON_Delete */
|
||||
CJSON_PUBLIC(cJSON *) cJSON_CreateStringReference(const char *string);
|
||||
/* Create an object/arrray that only references it's elements so
|
||||
* they will not be freed by cJSON_Delete */
|
||||
CJSON_PUBLIC(cJSON *) cJSON_CreateObjectReference(const cJSON *child);
|
||||
CJSON_PUBLIC(cJSON *) cJSON_CreateArrayReference(const cJSON *child);
|
||||
|
||||
/* These utilities create an Array of count items. */
|
||||
CJSON_PUBLIC(cJSON *) cJSON_CreateIntArray(const int *numbers, int count);
|
||||
CJSON_PUBLIC(cJSON *) cJSON_CreateFloatArray(const float *numbers, int count);
|
||||
CJSON_PUBLIC(cJSON *) cJSON_CreateDoubleArray(const double *numbers, int count);
|
||||
CJSON_PUBLIC(cJSON *) cJSON_CreateStringArray(const char **strings, int count);
|
||||
|
||||
/* Append item to the specified array/object. */
|
||||
CJSON_PUBLIC(void) cJSON_AddItemToArray(cJSON *array, cJSON *item);
|
||||
CJSON_PUBLIC(void) cJSON_AddItemToObject(cJSON *object, const char *string, cJSON *item);
|
||||
/* Use this when string is definitely const (i.e. a literal, or as good as), and will definitely survive the cJSON object.
|
||||
* WARNING: When this function was used, make sure to always check that (item->type & cJSON_StringIsConst) is zero before
|
||||
* writing to `item->string` */
|
||||
CJSON_PUBLIC(void) cJSON_AddItemToObjectCS(cJSON *object, const char *string, cJSON *item);
|
||||
/* Append reference to item to the specified array/object. Use this when you want to add an existing cJSON to a new cJSON, but don't want to corrupt your existing cJSON. */
|
||||
CJSON_PUBLIC(void) cJSON_AddItemReferenceToArray(cJSON *array, cJSON *item);
|
||||
CJSON_PUBLIC(void) cJSON_AddItemReferenceToObject(cJSON *object, const char *string, cJSON *item);
|
||||
|
||||
/* Remove/Detatch items from Arrays/Objects. */
|
||||
CJSON_PUBLIC(cJSON *) cJSON_DetachItemViaPointer(cJSON *parent, cJSON * const item);
|
||||
CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromArray(cJSON *array, int which);
|
||||
CJSON_PUBLIC(void) cJSON_DeleteItemFromArray(cJSON *array, int which);
|
||||
CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObject(cJSON *object, const char *string);
|
||||
CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObjectCaseSensitive(cJSON *object, const char *string);
|
||||
CJSON_PUBLIC(void) cJSON_DeleteItemFromObject(cJSON *object, const char *string);
|
||||
CJSON_PUBLIC(void) cJSON_DeleteItemFromObjectCaseSensitive(cJSON *object, const char *string);
|
||||
|
||||
/* Update array items. */
|
||||
CJSON_PUBLIC(void) cJSON_InsertItemInArray(cJSON *array, int which, cJSON *newitem); /* Shifts pre-existing items to the right. */
|
||||
CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemViaPointer(cJSON * const parent, cJSON * const item, cJSON * replacement);
|
||||
CJSON_PUBLIC(void) cJSON_ReplaceItemInArray(cJSON *array, int which, cJSON *newitem);
|
||||
CJSON_PUBLIC(void) cJSON_ReplaceItemInObject(cJSON *object,const char *string,cJSON *newitem);
|
||||
CJSON_PUBLIC(void) cJSON_ReplaceItemInObjectCaseSensitive(cJSON *object,const char *string,cJSON *newitem);
|
||||
|
||||
/* Duplicate a cJSON item */
|
||||
CJSON_PUBLIC(cJSON *) cJSON_Duplicate(const cJSON *item, cJSON_bool recurse);
|
||||
/* Duplicate will create a new, identical cJSON item to the one you pass, in new memory that will
|
||||
need to be released. With recurse!=0, it will duplicate any children connected to the item.
|
||||
The item->next and ->prev pointers are always zero on return from Duplicate. */
|
||||
/* Recursively compare two cJSON items for equality. If either a or b is NULL or invalid, they will be considered unequal.
|
||||
* case_sensitive determines if object keys are treated case sensitive (1) or case insensitive (0) */
|
||||
CJSON_PUBLIC(cJSON_bool) cJSON_Compare(const cJSON * const a, const cJSON * const b, const cJSON_bool case_sensitive);
|
||||
|
||||
|
||||
CJSON_PUBLIC(void) cJSON_Minify(char *json);
|
||||
|
||||
/* Helper functions for creating and adding items to an object at the same time.
|
||||
* They return the added item or NULL on failure. */
|
||||
CJSON_PUBLIC(cJSON*) cJSON_AddNullToObject(cJSON * const object, const char * const name);
|
||||
CJSON_PUBLIC(cJSON*) cJSON_AddTrueToObject(cJSON * const object, const char * const name);
|
||||
CJSON_PUBLIC(cJSON*) cJSON_AddFalseToObject(cJSON * const object, const char * const name);
|
||||
CJSON_PUBLIC(cJSON*) cJSON_AddBoolToObject(cJSON * const object, const char * const name, const cJSON_bool boolean);
|
||||
CJSON_PUBLIC(cJSON*) cJSON_AddNumberToObject(cJSON * const object, const char * const name, const double number);
|
||||
CJSON_PUBLIC(cJSON*) cJSON_AddStringToObject(cJSON * const object, const char * const name, const char * const string);
|
||||
CJSON_PUBLIC(cJSON*) cJSON_AddRawToObject(cJSON * const object, const char * const name, const char * const raw);
|
||||
CJSON_PUBLIC(cJSON*) cJSON_AddObjectToObject(cJSON * const object, const char * const name);
|
||||
CJSON_PUBLIC(cJSON*) cJSON_AddArrayToObject(cJSON * const object, const char * const name);
|
||||
|
||||
/* When assigning an integer value, it needs to be propagated to valuedouble too. */
|
||||
#define cJSON_SetIntValue(object, number) ((object) ? (object)->valueint = (object)->valuedouble = (number) : (number))
|
||||
/* helper for the cJSON_SetNumberValue macro */
|
||||
CJSON_PUBLIC(double) cJSON_SetNumberHelper(cJSON *object, double number);
|
||||
#define cJSON_SetNumberValue(object, number) ((object != NULL) ? cJSON_SetNumberHelper(object, (double)number) : (number))
|
||||
|
||||
/* Macro for iterating over an array or object */
|
||||
#define cJSON_ArrayForEach(element, array) for(element = (array != NULL) ? (array)->child : NULL; element != NULL; element = element->next)
|
||||
|
||||
/* malloc/free objects using the malloc/free functions that have been set with cJSON_InitHooks */
|
||||
CJSON_PUBLIC(void *) cJSON_malloc(size_t size);
|
||||
CJSON_PUBLIC(void) cJSON_free(void *object);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
27
external/fmtlib/LICENSE.rst
vendored
Normal file
27
external/fmtlib/LICENSE.rst
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
Copyright (c) 2012 - present, Victor Zverovich
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
--- Optional exception to the license ---
|
||||
|
||||
As an exception, if, as a result of your compiling your source code, portions
|
||||
of this Software are embedded into a machine-executable object form of such
|
||||
source code, you may redistribute such embedded portions in such object form
|
||||
without including the above copyright and permission notices.
|
||||
12
external/fmtlib/README.opm
vendored
Normal file
12
external/fmtlib/README.opm
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
The include/ directory is a copy of the include directory from version 7.0.3 of
|
||||
the fmtlib distribution. The fmtlib can be found at https://github.com/fmtlib/fmt
|
||||
|
||||
The fmtlib code embedded here should be compiled in header only mode, to ensure
|
||||
that the symbol FMT_HEADER_ONLY must be defined before the the fmt/format.h
|
||||
header is included:
|
||||
|
||||
#define FMT_HEADER_ONLY
|
||||
#include <fmt/format.h>
|
||||
|
||||
....
|
||||
auto msg = fmt::format("Hello {}", "world");
|
||||
1123
external/fmtlib/include/fmt/chrono.h
vendored
Normal file
1123
external/fmtlib/include/fmt/chrono.h
vendored
Normal file
File diff suppressed because it is too large
Load Diff
566
external/fmtlib/include/fmt/color.h
vendored
Normal file
566
external/fmtlib/include/fmt/color.h
vendored
Normal file
@@ -0,0 +1,566 @@
|
||||
// Formatting library for C++ - color support
|
||||
//
|
||||
// Copyright (c) 2018 - present, Victor Zverovich and fmt contributors
|
||||
// All rights reserved.
|
||||
//
|
||||
// For the license information refer to format.h.
|
||||
|
||||
#ifndef FMT_COLOR_H_
|
||||
#define FMT_COLOR_H_
|
||||
|
||||
#include "format.h"
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
|
||||
enum class color : uint32_t {
|
||||
alice_blue = 0xF0F8FF, // rgb(240,248,255)
|
||||
antique_white = 0xFAEBD7, // rgb(250,235,215)
|
||||
aqua = 0x00FFFF, // rgb(0,255,255)
|
||||
aquamarine = 0x7FFFD4, // rgb(127,255,212)
|
||||
azure = 0xF0FFFF, // rgb(240,255,255)
|
||||
beige = 0xF5F5DC, // rgb(245,245,220)
|
||||
bisque = 0xFFE4C4, // rgb(255,228,196)
|
||||
black = 0x000000, // rgb(0,0,0)
|
||||
blanched_almond = 0xFFEBCD, // rgb(255,235,205)
|
||||
blue = 0x0000FF, // rgb(0,0,255)
|
||||
blue_violet = 0x8A2BE2, // rgb(138,43,226)
|
||||
brown = 0xA52A2A, // rgb(165,42,42)
|
||||
burly_wood = 0xDEB887, // rgb(222,184,135)
|
||||
cadet_blue = 0x5F9EA0, // rgb(95,158,160)
|
||||
chartreuse = 0x7FFF00, // rgb(127,255,0)
|
||||
chocolate = 0xD2691E, // rgb(210,105,30)
|
||||
coral = 0xFF7F50, // rgb(255,127,80)
|
||||
cornflower_blue = 0x6495ED, // rgb(100,149,237)
|
||||
cornsilk = 0xFFF8DC, // rgb(255,248,220)
|
||||
crimson = 0xDC143C, // rgb(220,20,60)
|
||||
cyan = 0x00FFFF, // rgb(0,255,255)
|
||||
dark_blue = 0x00008B, // rgb(0,0,139)
|
||||
dark_cyan = 0x008B8B, // rgb(0,139,139)
|
||||
dark_golden_rod = 0xB8860B, // rgb(184,134,11)
|
||||
dark_gray = 0xA9A9A9, // rgb(169,169,169)
|
||||
dark_green = 0x006400, // rgb(0,100,0)
|
||||
dark_khaki = 0xBDB76B, // rgb(189,183,107)
|
||||
dark_magenta = 0x8B008B, // rgb(139,0,139)
|
||||
dark_olive_green = 0x556B2F, // rgb(85,107,47)
|
||||
dark_orange = 0xFF8C00, // rgb(255,140,0)
|
||||
dark_orchid = 0x9932CC, // rgb(153,50,204)
|
||||
dark_red = 0x8B0000, // rgb(139,0,0)
|
||||
dark_salmon = 0xE9967A, // rgb(233,150,122)
|
||||
dark_sea_green = 0x8FBC8F, // rgb(143,188,143)
|
||||
dark_slate_blue = 0x483D8B, // rgb(72,61,139)
|
||||
dark_slate_gray = 0x2F4F4F, // rgb(47,79,79)
|
||||
dark_turquoise = 0x00CED1, // rgb(0,206,209)
|
||||
dark_violet = 0x9400D3, // rgb(148,0,211)
|
||||
deep_pink = 0xFF1493, // rgb(255,20,147)
|
||||
deep_sky_blue = 0x00BFFF, // rgb(0,191,255)
|
||||
dim_gray = 0x696969, // rgb(105,105,105)
|
||||
dodger_blue = 0x1E90FF, // rgb(30,144,255)
|
||||
fire_brick = 0xB22222, // rgb(178,34,34)
|
||||
floral_white = 0xFFFAF0, // rgb(255,250,240)
|
||||
forest_green = 0x228B22, // rgb(34,139,34)
|
||||
fuchsia = 0xFF00FF, // rgb(255,0,255)
|
||||
gainsboro = 0xDCDCDC, // rgb(220,220,220)
|
||||
ghost_white = 0xF8F8FF, // rgb(248,248,255)
|
||||
gold = 0xFFD700, // rgb(255,215,0)
|
||||
golden_rod = 0xDAA520, // rgb(218,165,32)
|
||||
gray = 0x808080, // rgb(128,128,128)
|
||||
green = 0x008000, // rgb(0,128,0)
|
||||
green_yellow = 0xADFF2F, // rgb(173,255,47)
|
||||
honey_dew = 0xF0FFF0, // rgb(240,255,240)
|
||||
hot_pink = 0xFF69B4, // rgb(255,105,180)
|
||||
indian_red = 0xCD5C5C, // rgb(205,92,92)
|
||||
indigo = 0x4B0082, // rgb(75,0,130)
|
||||
ivory = 0xFFFFF0, // rgb(255,255,240)
|
||||
khaki = 0xF0E68C, // rgb(240,230,140)
|
||||
lavender = 0xE6E6FA, // rgb(230,230,250)
|
||||
lavender_blush = 0xFFF0F5, // rgb(255,240,245)
|
||||
lawn_green = 0x7CFC00, // rgb(124,252,0)
|
||||
lemon_chiffon = 0xFFFACD, // rgb(255,250,205)
|
||||
light_blue = 0xADD8E6, // rgb(173,216,230)
|
||||
light_coral = 0xF08080, // rgb(240,128,128)
|
||||
light_cyan = 0xE0FFFF, // rgb(224,255,255)
|
||||
light_golden_rod_yellow = 0xFAFAD2, // rgb(250,250,210)
|
||||
light_gray = 0xD3D3D3, // rgb(211,211,211)
|
||||
light_green = 0x90EE90, // rgb(144,238,144)
|
||||
light_pink = 0xFFB6C1, // rgb(255,182,193)
|
||||
light_salmon = 0xFFA07A, // rgb(255,160,122)
|
||||
light_sea_green = 0x20B2AA, // rgb(32,178,170)
|
||||
light_sky_blue = 0x87CEFA, // rgb(135,206,250)
|
||||
light_slate_gray = 0x778899, // rgb(119,136,153)
|
||||
light_steel_blue = 0xB0C4DE, // rgb(176,196,222)
|
||||
light_yellow = 0xFFFFE0, // rgb(255,255,224)
|
||||
lime = 0x00FF00, // rgb(0,255,0)
|
||||
lime_green = 0x32CD32, // rgb(50,205,50)
|
||||
linen = 0xFAF0E6, // rgb(250,240,230)
|
||||
magenta = 0xFF00FF, // rgb(255,0,255)
|
||||
maroon = 0x800000, // rgb(128,0,0)
|
||||
medium_aquamarine = 0x66CDAA, // rgb(102,205,170)
|
||||
medium_blue = 0x0000CD, // rgb(0,0,205)
|
||||
medium_orchid = 0xBA55D3, // rgb(186,85,211)
|
||||
medium_purple = 0x9370DB, // rgb(147,112,219)
|
||||
medium_sea_green = 0x3CB371, // rgb(60,179,113)
|
||||
medium_slate_blue = 0x7B68EE, // rgb(123,104,238)
|
||||
medium_spring_green = 0x00FA9A, // rgb(0,250,154)
|
||||
medium_turquoise = 0x48D1CC, // rgb(72,209,204)
|
||||
medium_violet_red = 0xC71585, // rgb(199,21,133)
|
||||
midnight_blue = 0x191970, // rgb(25,25,112)
|
||||
mint_cream = 0xF5FFFA, // rgb(245,255,250)
|
||||
misty_rose = 0xFFE4E1, // rgb(255,228,225)
|
||||
moccasin = 0xFFE4B5, // rgb(255,228,181)
|
||||
navajo_white = 0xFFDEAD, // rgb(255,222,173)
|
||||
navy = 0x000080, // rgb(0,0,128)
|
||||
old_lace = 0xFDF5E6, // rgb(253,245,230)
|
||||
olive = 0x808000, // rgb(128,128,0)
|
||||
olive_drab = 0x6B8E23, // rgb(107,142,35)
|
||||
orange = 0xFFA500, // rgb(255,165,0)
|
||||
orange_red = 0xFF4500, // rgb(255,69,0)
|
||||
orchid = 0xDA70D6, // rgb(218,112,214)
|
||||
pale_golden_rod = 0xEEE8AA, // rgb(238,232,170)
|
||||
pale_green = 0x98FB98, // rgb(152,251,152)
|
||||
pale_turquoise = 0xAFEEEE, // rgb(175,238,238)
|
||||
pale_violet_red = 0xDB7093, // rgb(219,112,147)
|
||||
papaya_whip = 0xFFEFD5, // rgb(255,239,213)
|
||||
peach_puff = 0xFFDAB9, // rgb(255,218,185)
|
||||
peru = 0xCD853F, // rgb(205,133,63)
|
||||
pink = 0xFFC0CB, // rgb(255,192,203)
|
||||
plum = 0xDDA0DD, // rgb(221,160,221)
|
||||
powder_blue = 0xB0E0E6, // rgb(176,224,230)
|
||||
purple = 0x800080, // rgb(128,0,128)
|
||||
rebecca_purple = 0x663399, // rgb(102,51,153)
|
||||
red = 0xFF0000, // rgb(255,0,0)
|
||||
rosy_brown = 0xBC8F8F, // rgb(188,143,143)
|
||||
royal_blue = 0x4169E1, // rgb(65,105,225)
|
||||
saddle_brown = 0x8B4513, // rgb(139,69,19)
|
||||
salmon = 0xFA8072, // rgb(250,128,114)
|
||||
sandy_brown = 0xF4A460, // rgb(244,164,96)
|
||||
sea_green = 0x2E8B57, // rgb(46,139,87)
|
||||
sea_shell = 0xFFF5EE, // rgb(255,245,238)
|
||||
sienna = 0xA0522D, // rgb(160,82,45)
|
||||
silver = 0xC0C0C0, // rgb(192,192,192)
|
||||
sky_blue = 0x87CEEB, // rgb(135,206,235)
|
||||
slate_blue = 0x6A5ACD, // rgb(106,90,205)
|
||||
slate_gray = 0x708090, // rgb(112,128,144)
|
||||
snow = 0xFFFAFA, // rgb(255,250,250)
|
||||
spring_green = 0x00FF7F, // rgb(0,255,127)
|
||||
steel_blue = 0x4682B4, // rgb(70,130,180)
|
||||
tan = 0xD2B48C, // rgb(210,180,140)
|
||||
teal = 0x008080, // rgb(0,128,128)
|
||||
thistle = 0xD8BFD8, // rgb(216,191,216)
|
||||
tomato = 0xFF6347, // rgb(255,99,71)
|
||||
turquoise = 0x40E0D0, // rgb(64,224,208)
|
||||
violet = 0xEE82EE, // rgb(238,130,238)
|
||||
wheat = 0xF5DEB3, // rgb(245,222,179)
|
||||
white = 0xFFFFFF, // rgb(255,255,255)
|
||||
white_smoke = 0xF5F5F5, // rgb(245,245,245)
|
||||
yellow = 0xFFFF00, // rgb(255,255,0)
|
||||
yellow_green = 0x9ACD32 // rgb(154,205,50)
|
||||
}; // enum class color
|
||||
|
||||
enum class terminal_color : uint8_t {
|
||||
black = 30,
|
||||
red,
|
||||
green,
|
||||
yellow,
|
||||
blue,
|
||||
magenta,
|
||||
cyan,
|
||||
white,
|
||||
bright_black = 90,
|
||||
bright_red,
|
||||
bright_green,
|
||||
bright_yellow,
|
||||
bright_blue,
|
||||
bright_magenta,
|
||||
bright_cyan,
|
||||
bright_white
|
||||
};
|
||||
|
||||
enum class emphasis : uint8_t {
|
||||
bold = 1,
|
||||
italic = 1 << 1,
|
||||
underline = 1 << 2,
|
||||
strikethrough = 1 << 3
|
||||
};
|
||||
|
||||
// rgb is a struct for red, green and blue colors.
|
||||
// Using the name "rgb" makes some editors show the color in a tooltip.
|
||||
struct rgb {
|
||||
FMT_CONSTEXPR rgb() : r(0), g(0), b(0) {}
|
||||
FMT_CONSTEXPR rgb(uint8_t r_, uint8_t g_, uint8_t b_) : r(r_), g(g_), b(b_) {}
|
||||
FMT_CONSTEXPR rgb(uint32_t hex)
|
||||
: r((hex >> 16) & 0xFF), g((hex >> 8) & 0xFF), b(hex & 0xFF) {}
|
||||
FMT_CONSTEXPR rgb(color hex)
|
||||
: r((uint32_t(hex) >> 16) & 0xFF),
|
||||
g((uint32_t(hex) >> 8) & 0xFF),
|
||||
b(uint32_t(hex) & 0xFF) {}
|
||||
uint8_t r;
|
||||
uint8_t g;
|
||||
uint8_t b;
|
||||
};
|
||||
|
||||
namespace detail {
|
||||
|
||||
// color is a struct of either a rgb color or a terminal color.
|
||||
struct color_type {
|
||||
FMT_CONSTEXPR color_type() FMT_NOEXCEPT : is_rgb(), value{} {}
|
||||
FMT_CONSTEXPR color_type(color rgb_color) FMT_NOEXCEPT : is_rgb(true),
|
||||
value{} {
|
||||
value.rgb_color = static_cast<uint32_t>(rgb_color);
|
||||
}
|
||||
FMT_CONSTEXPR color_type(rgb rgb_color) FMT_NOEXCEPT : is_rgb(true), value{} {
|
||||
value.rgb_color = (static_cast<uint32_t>(rgb_color.r) << 16) |
|
||||
(static_cast<uint32_t>(rgb_color.g) << 8) | rgb_color.b;
|
||||
}
|
||||
FMT_CONSTEXPR color_type(terminal_color term_color) FMT_NOEXCEPT : is_rgb(),
|
||||
value{} {
|
||||
value.term_color = static_cast<uint8_t>(term_color);
|
||||
}
|
||||
bool is_rgb;
|
||||
union color_union {
|
||||
uint8_t term_color;
|
||||
uint32_t rgb_color;
|
||||
} value;
|
||||
};
|
||||
} // namespace detail
|
||||
|
||||
// Experimental text formatting support.
|
||||
class text_style {
|
||||
public:
|
||||
FMT_CONSTEXPR text_style(emphasis em = emphasis()) FMT_NOEXCEPT
|
||||
: set_foreground_color(),
|
||||
set_background_color(),
|
||||
ems(em) {}
|
||||
|
||||
FMT_CONSTEXPR text_style& operator|=(const text_style& rhs) {
|
||||
if (!set_foreground_color) {
|
||||
set_foreground_color = rhs.set_foreground_color;
|
||||
foreground_color = rhs.foreground_color;
|
||||
} else if (rhs.set_foreground_color) {
|
||||
if (!foreground_color.is_rgb || !rhs.foreground_color.is_rgb)
|
||||
FMT_THROW(format_error("can't OR a terminal color"));
|
||||
foreground_color.value.rgb_color |= rhs.foreground_color.value.rgb_color;
|
||||
}
|
||||
|
||||
if (!set_background_color) {
|
||||
set_background_color = rhs.set_background_color;
|
||||
background_color = rhs.background_color;
|
||||
} else if (rhs.set_background_color) {
|
||||
if (!background_color.is_rgb || !rhs.background_color.is_rgb)
|
||||
FMT_THROW(format_error("can't OR a terminal color"));
|
||||
background_color.value.rgb_color |= rhs.background_color.value.rgb_color;
|
||||
}
|
||||
|
||||
ems = static_cast<emphasis>(static_cast<uint8_t>(ems) |
|
||||
static_cast<uint8_t>(rhs.ems));
|
||||
return *this;
|
||||
}
|
||||
|
||||
friend FMT_CONSTEXPR text_style operator|(text_style lhs,
|
||||
const text_style& rhs) {
|
||||
return lhs |= rhs;
|
||||
}
|
||||
|
||||
FMT_CONSTEXPR text_style& operator&=(const text_style& rhs) {
|
||||
if (!set_foreground_color) {
|
||||
set_foreground_color = rhs.set_foreground_color;
|
||||
foreground_color = rhs.foreground_color;
|
||||
} else if (rhs.set_foreground_color) {
|
||||
if (!foreground_color.is_rgb || !rhs.foreground_color.is_rgb)
|
||||
FMT_THROW(format_error("can't AND a terminal color"));
|
||||
foreground_color.value.rgb_color &= rhs.foreground_color.value.rgb_color;
|
||||
}
|
||||
|
||||
if (!set_background_color) {
|
||||
set_background_color = rhs.set_background_color;
|
||||
background_color = rhs.background_color;
|
||||
} else if (rhs.set_background_color) {
|
||||
if (!background_color.is_rgb || !rhs.background_color.is_rgb)
|
||||
FMT_THROW(format_error("can't AND a terminal color"));
|
||||
background_color.value.rgb_color &= rhs.background_color.value.rgb_color;
|
||||
}
|
||||
|
||||
ems = static_cast<emphasis>(static_cast<uint8_t>(ems) &
|
||||
static_cast<uint8_t>(rhs.ems));
|
||||
return *this;
|
||||
}
|
||||
|
||||
friend FMT_CONSTEXPR text_style operator&(text_style lhs,
|
||||
const text_style& rhs) {
|
||||
return lhs &= rhs;
|
||||
}
|
||||
|
||||
FMT_CONSTEXPR bool has_foreground() const FMT_NOEXCEPT {
|
||||
return set_foreground_color;
|
||||
}
|
||||
FMT_CONSTEXPR bool has_background() const FMT_NOEXCEPT {
|
||||
return set_background_color;
|
||||
}
|
||||
FMT_CONSTEXPR bool has_emphasis() const FMT_NOEXCEPT {
|
||||
return static_cast<uint8_t>(ems) != 0;
|
||||
}
|
||||
FMT_CONSTEXPR detail::color_type get_foreground() const FMT_NOEXCEPT {
|
||||
FMT_ASSERT(has_foreground(), "no foreground specified for this style");
|
||||
return foreground_color;
|
||||
}
|
||||
FMT_CONSTEXPR detail::color_type get_background() const FMT_NOEXCEPT {
|
||||
FMT_ASSERT(has_background(), "no background specified for this style");
|
||||
return background_color;
|
||||
}
|
||||
FMT_CONSTEXPR emphasis get_emphasis() const FMT_NOEXCEPT {
|
||||
FMT_ASSERT(has_emphasis(), "no emphasis specified for this style");
|
||||
return ems;
|
||||
}
|
||||
|
||||
private:
|
||||
FMT_CONSTEXPR text_style(bool is_foreground,
|
||||
detail::color_type text_color) FMT_NOEXCEPT
|
||||
: set_foreground_color(),
|
||||
set_background_color(),
|
||||
ems() {
|
||||
if (is_foreground) {
|
||||
foreground_color = text_color;
|
||||
set_foreground_color = true;
|
||||
} else {
|
||||
background_color = text_color;
|
||||
set_background_color = true;
|
||||
}
|
||||
}
|
||||
|
||||
friend FMT_CONSTEXPR_DECL text_style fg(detail::color_type foreground)
|
||||
FMT_NOEXCEPT;
|
||||
friend FMT_CONSTEXPR_DECL text_style bg(detail::color_type background)
|
||||
FMT_NOEXCEPT;
|
||||
|
||||
detail::color_type foreground_color;
|
||||
detail::color_type background_color;
|
||||
bool set_foreground_color;
|
||||
bool set_background_color;
|
||||
emphasis ems;
|
||||
};
|
||||
|
||||
FMT_CONSTEXPR text_style fg(detail::color_type foreground) FMT_NOEXCEPT {
|
||||
return text_style(/*is_foreground=*/true, foreground);
|
||||
}
|
||||
|
||||
FMT_CONSTEXPR text_style bg(detail::color_type background) FMT_NOEXCEPT {
|
||||
return text_style(/*is_foreground=*/false, background);
|
||||
}
|
||||
|
||||
FMT_CONSTEXPR text_style operator|(emphasis lhs, emphasis rhs) FMT_NOEXCEPT {
|
||||
return text_style(lhs) | rhs;
|
||||
}
|
||||
|
||||
namespace detail {
|
||||
|
||||
template <typename Char> struct ansi_color_escape {
|
||||
FMT_CONSTEXPR ansi_color_escape(detail::color_type text_color,
|
||||
const char* esc) FMT_NOEXCEPT {
|
||||
// If we have a terminal color, we need to output another escape code
|
||||
// sequence.
|
||||
if (!text_color.is_rgb) {
|
||||
bool is_background = esc == detail::data::background_color;
|
||||
uint32_t value = text_color.value.term_color;
|
||||
// Background ASCII codes are the same as the foreground ones but with
|
||||
// 10 more.
|
||||
if (is_background) value += 10u;
|
||||
|
||||
size_t index = 0;
|
||||
buffer[index++] = static_cast<Char>('\x1b');
|
||||
buffer[index++] = static_cast<Char>('[');
|
||||
|
||||
if (value >= 100u) {
|
||||
buffer[index++] = static_cast<Char>('1');
|
||||
value %= 100u;
|
||||
}
|
||||
buffer[index++] = static_cast<Char>('0' + value / 10u);
|
||||
buffer[index++] = static_cast<Char>('0' + value % 10u);
|
||||
|
||||
buffer[index++] = static_cast<Char>('m');
|
||||
buffer[index++] = static_cast<Char>('\0');
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < 7; i++) {
|
||||
buffer[i] = static_cast<Char>(esc[i]);
|
||||
}
|
||||
rgb color(text_color.value.rgb_color);
|
||||
to_esc(color.r, buffer + 7, ';');
|
||||
to_esc(color.g, buffer + 11, ';');
|
||||
to_esc(color.b, buffer + 15, 'm');
|
||||
buffer[19] = static_cast<Char>(0);
|
||||
}
|
||||
FMT_CONSTEXPR ansi_color_escape(emphasis em) FMT_NOEXCEPT {
|
||||
uint8_t em_codes[4] = {};
|
||||
uint8_t em_bits = static_cast<uint8_t>(em);
|
||||
if (em_bits & static_cast<uint8_t>(emphasis::bold)) em_codes[0] = 1;
|
||||
if (em_bits & static_cast<uint8_t>(emphasis::italic)) em_codes[1] = 3;
|
||||
if (em_bits & static_cast<uint8_t>(emphasis::underline)) em_codes[2] = 4;
|
||||
if (em_bits & static_cast<uint8_t>(emphasis::strikethrough))
|
||||
em_codes[3] = 9;
|
||||
|
||||
size_t index = 0;
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
if (!em_codes[i]) continue;
|
||||
buffer[index++] = static_cast<Char>('\x1b');
|
||||
buffer[index++] = static_cast<Char>('[');
|
||||
buffer[index++] = static_cast<Char>('0' + em_codes[i]);
|
||||
buffer[index++] = static_cast<Char>('m');
|
||||
}
|
||||
buffer[index++] = static_cast<Char>(0);
|
||||
}
|
||||
FMT_CONSTEXPR operator const Char*() const FMT_NOEXCEPT { return buffer; }
|
||||
|
||||
FMT_CONSTEXPR const Char* begin() const FMT_NOEXCEPT { return buffer; }
|
||||
FMT_CONSTEXPR const Char* end() const FMT_NOEXCEPT {
|
||||
return buffer + std::char_traits<Char>::length(buffer);
|
||||
}
|
||||
|
||||
private:
|
||||
Char buffer[7u + 3u * 4u + 1u];
|
||||
|
||||
static FMT_CONSTEXPR void to_esc(uint8_t c, Char* out,
|
||||
char delimiter) FMT_NOEXCEPT {
|
||||
out[0] = static_cast<Char>('0' + c / 100);
|
||||
out[1] = static_cast<Char>('0' + c / 10 % 10);
|
||||
out[2] = static_cast<Char>('0' + c % 10);
|
||||
out[3] = static_cast<Char>(delimiter);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Char>
|
||||
FMT_CONSTEXPR ansi_color_escape<Char> make_foreground_color(
|
||||
detail::color_type foreground) FMT_NOEXCEPT {
|
||||
return ansi_color_escape<Char>(foreground, detail::data::foreground_color);
|
||||
}
|
||||
|
||||
template <typename Char>
|
||||
FMT_CONSTEXPR ansi_color_escape<Char> make_background_color(
|
||||
detail::color_type background) FMT_NOEXCEPT {
|
||||
return ansi_color_escape<Char>(background, detail::data::background_color);
|
||||
}
|
||||
|
||||
template <typename Char>
|
||||
FMT_CONSTEXPR ansi_color_escape<Char> make_emphasis(emphasis em) FMT_NOEXCEPT {
|
||||
return ansi_color_escape<Char>(em);
|
||||
}
|
||||
|
||||
template <typename Char>
|
||||
inline void fputs(const Char* chars, FILE* stream) FMT_NOEXCEPT {
|
||||
std::fputs(chars, stream);
|
||||
}
|
||||
|
||||
template <>
|
||||
inline void fputs<wchar_t>(const wchar_t* chars, FILE* stream) FMT_NOEXCEPT {
|
||||
std::fputws(chars, stream);
|
||||
}
|
||||
|
||||
template <typename Char> inline void reset_color(FILE* stream) FMT_NOEXCEPT {
|
||||
fputs(detail::data::reset_color, stream);
|
||||
}
|
||||
|
||||
template <> inline void reset_color<wchar_t>(FILE* stream) FMT_NOEXCEPT {
|
||||
fputs(detail::data::wreset_color, stream);
|
||||
}
|
||||
|
||||
template <typename Char>
|
||||
inline void reset_color(basic_memory_buffer<Char>& buffer) FMT_NOEXCEPT {
|
||||
const char* begin = data::reset_color;
|
||||
const char* end = begin + sizeof(data::reset_color) - 1;
|
||||
buffer.append(begin, end);
|
||||
}
|
||||
|
||||
template <typename Char>
|
||||
void vformat_to(basic_memory_buffer<Char>& buf, const text_style& ts,
|
||||
basic_string_view<Char> format_str,
|
||||
basic_format_args<buffer_context<Char>> args) {
|
||||
bool has_style = false;
|
||||
if (ts.has_emphasis()) {
|
||||
has_style = true;
|
||||
auto emphasis = detail::make_emphasis<Char>(ts.get_emphasis());
|
||||
buf.append(emphasis.begin(), emphasis.end());
|
||||
}
|
||||
if (ts.has_foreground()) {
|
||||
has_style = true;
|
||||
auto foreground = detail::make_foreground_color<Char>(ts.get_foreground());
|
||||
buf.append(foreground.begin(), foreground.end());
|
||||
}
|
||||
if (ts.has_background()) {
|
||||
has_style = true;
|
||||
auto background = detail::make_background_color<Char>(ts.get_background());
|
||||
buf.append(background.begin(), background.end());
|
||||
}
|
||||
detail::vformat_to(buf, format_str, args);
|
||||
if (has_style) detail::reset_color<Char>(buf);
|
||||
}
|
||||
} // namespace detail
|
||||
|
||||
template <typename S, typename Char = char_t<S>>
|
||||
void vprint(std::FILE* f, const text_style& ts, const S& format,
|
||||
basic_format_args<buffer_context<Char>> args) {
|
||||
basic_memory_buffer<Char> buf;
|
||||
detail::vformat_to(buf, ts, to_string_view(format), args);
|
||||
buf.push_back(Char(0));
|
||||
detail::fputs(buf.data(), f);
|
||||
}
|
||||
|
||||
/**
|
||||
Formats a string and prints it to the specified file stream using ANSI
|
||||
escape sequences to specify text formatting.
|
||||
Example:
|
||||
fmt::print(fmt::emphasis::bold | fg(fmt::color::red),
|
||||
"Elapsed time: {0:.2f} seconds", 1.23);
|
||||
*/
|
||||
template <typename S, typename... Args,
|
||||
FMT_ENABLE_IF(detail::is_string<S>::value)>
|
||||
void print(std::FILE* f, const text_style& ts, const S& format_str,
|
||||
const Args&... args) {
|
||||
detail::check_format_string<Args...>(format_str);
|
||||
using context = buffer_context<char_t<S>>;
|
||||
format_arg_store<context, Args...> as{args...};
|
||||
vprint(f, ts, format_str, basic_format_args<context>(as));
|
||||
}
|
||||
|
||||
/**
|
||||
Formats a string and prints it to stdout using ANSI escape sequences to
|
||||
specify text formatting.
|
||||
Example:
|
||||
fmt::print(fmt::emphasis::bold | fg(fmt::color::red),
|
||||
"Elapsed time: {0:.2f} seconds", 1.23);
|
||||
*/
|
||||
template <typename S, typename... Args,
|
||||
FMT_ENABLE_IF(detail::is_string<S>::value)>
|
||||
void print(const text_style& ts, const S& format_str, const Args&... args) {
|
||||
return print(stdout, ts, format_str, args...);
|
||||
}
|
||||
|
||||
template <typename S, typename Char = char_t<S>>
|
||||
inline std::basic_string<Char> vformat(
|
||||
const text_style& ts, const S& format_str,
|
||||
basic_format_args<buffer_context<type_identity_t<Char>>> args) {
|
||||
basic_memory_buffer<Char> buf;
|
||||
detail::vformat_to(buf, ts, to_string_view(format_str), args);
|
||||
return fmt::to_string(buf);
|
||||
}
|
||||
|
||||
/**
|
||||
\rst
|
||||
Formats arguments and returns the result as a string using ANSI
|
||||
escape sequences to specify text formatting.
|
||||
|
||||
**Example**::
|
||||
|
||||
#include <fmt/color.h>
|
||||
std::string message = fmt::format(fmt::emphasis::bold | fg(fmt::color::red),
|
||||
"The answer is {}", 42);
|
||||
\endrst
|
||||
*/
|
||||
template <typename S, typename... Args, typename Char = char_t<S>>
|
||||
inline std::basic_string<Char> format(const text_style& ts, const S& format_str,
|
||||
const Args&... args) {
|
||||
return vformat(ts, to_string_view(format_str),
|
||||
detail::make_args_checked<Args...>(format_str, args...));
|
||||
}
|
||||
|
||||
FMT_END_NAMESPACE
|
||||
|
||||
#endif // FMT_COLOR_H_
|
||||
665
external/fmtlib/include/fmt/compile.h
vendored
Normal file
665
external/fmtlib/include/fmt/compile.h
vendored
Normal file
@@ -0,0 +1,665 @@
|
||||
// Formatting library for C++ - experimental format string compilation
|
||||
//
|
||||
// Copyright (c) 2012 - present, Victor Zverovich and fmt contributors
|
||||
// All rights reserved.
|
||||
//
|
||||
// For the license information refer to format.h.
|
||||
|
||||
#ifndef FMT_COMPILE_H_
|
||||
#define FMT_COMPILE_H_
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "format.h"
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
namespace detail {
|
||||
|
||||
// A compile-time string which is compiled into fast formatting code.
|
||||
class compiled_string {};
|
||||
|
||||
template <typename S>
|
||||
struct is_compiled_string : std::is_base_of<compiled_string, S> {};
|
||||
|
||||
/**
|
||||
\rst
|
||||
Converts a string literal *s* into a format string that will be parsed at
|
||||
compile time and converted into efficient formatting code. Requires C++17
|
||||
``constexpr if`` compiler support.
|
||||
|
||||
**Example**::
|
||||
|
||||
// Converts 42 into std::string using the most efficient method and no
|
||||
// runtime format string processing.
|
||||
std::string s = fmt::format(FMT_COMPILE("{}"), 42);
|
||||
\endrst
|
||||
*/
|
||||
#define FMT_COMPILE(s) FMT_STRING_IMPL(s, fmt::detail::compiled_string)
|
||||
|
||||
template <typename T, typename... Tail>
|
||||
const T& first(const T& value, const Tail&...) {
|
||||
return value;
|
||||
}
|
||||
|
||||
// Part of a compiled format string. It can be either literal text or a
|
||||
// replacement field.
|
||||
template <typename Char> struct format_part {
|
||||
enum class kind { arg_index, arg_name, text, replacement };
|
||||
|
||||
struct replacement {
|
||||
arg_ref<Char> arg_id;
|
||||
dynamic_format_specs<Char> specs;
|
||||
};
|
||||
|
||||
kind part_kind;
|
||||
union value {
|
||||
int arg_index;
|
||||
basic_string_view<Char> str;
|
||||
replacement repl;
|
||||
|
||||
FMT_CONSTEXPR value(int index = 0) : arg_index(index) {}
|
||||
FMT_CONSTEXPR value(basic_string_view<Char> s) : str(s) {}
|
||||
FMT_CONSTEXPR value(replacement r) : repl(r) {}
|
||||
} val;
|
||||
// Position past the end of the argument id.
|
||||
const Char* arg_id_end = nullptr;
|
||||
|
||||
FMT_CONSTEXPR format_part(kind k = kind::arg_index, value v = {})
|
||||
: part_kind(k), val(v) {}
|
||||
|
||||
static FMT_CONSTEXPR format_part make_arg_index(int index) {
|
||||
return format_part(kind::arg_index, index);
|
||||
}
|
||||
static FMT_CONSTEXPR format_part make_arg_name(basic_string_view<Char> name) {
|
||||
return format_part(kind::arg_name, name);
|
||||
}
|
||||
static FMT_CONSTEXPR format_part make_text(basic_string_view<Char> text) {
|
||||
return format_part(kind::text, text);
|
||||
}
|
||||
static FMT_CONSTEXPR format_part make_replacement(replacement repl) {
|
||||
return format_part(kind::replacement, repl);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Char> struct part_counter {
|
||||
unsigned num_parts = 0;
|
||||
|
||||
FMT_CONSTEXPR void on_text(const Char* begin, const Char* end) {
|
||||
if (begin != end) ++num_parts;
|
||||
}
|
||||
|
||||
FMT_CONSTEXPR int on_arg_id() { return ++num_parts, 0; }
|
||||
FMT_CONSTEXPR int on_arg_id(int) { return ++num_parts, 0; }
|
||||
FMT_CONSTEXPR int on_arg_id(basic_string_view<Char>) {
|
||||
return ++num_parts, 0;
|
||||
}
|
||||
|
||||
FMT_CONSTEXPR void on_replacement_field(int, const Char*) {}
|
||||
|
||||
FMT_CONSTEXPR const Char* on_format_specs(int, const Char* begin,
|
||||
const Char* end) {
|
||||
// Find the matching brace.
|
||||
unsigned brace_counter = 0;
|
||||
for (; begin != end; ++begin) {
|
||||
if (*begin == '{') {
|
||||
++brace_counter;
|
||||
} else if (*begin == '}') {
|
||||
if (brace_counter == 0u) break;
|
||||
--brace_counter;
|
||||
}
|
||||
}
|
||||
return begin;
|
||||
}
|
||||
|
||||
FMT_CONSTEXPR void on_error(const char*) {}
|
||||
};
|
||||
|
||||
// Counts the number of parts in a format string.
|
||||
template <typename Char>
|
||||
FMT_CONSTEXPR unsigned count_parts(basic_string_view<Char> format_str) {
|
||||
part_counter<Char> counter;
|
||||
parse_format_string<true>(format_str, counter);
|
||||
return counter.num_parts;
|
||||
}
|
||||
|
||||
template <typename Char, typename PartHandler>
|
||||
class format_string_compiler : public error_handler {
|
||||
private:
|
||||
using part = format_part<Char>;
|
||||
|
||||
PartHandler handler_;
|
||||
part part_;
|
||||
basic_string_view<Char> format_str_;
|
||||
basic_format_parse_context<Char> parse_context_;
|
||||
|
||||
public:
|
||||
FMT_CONSTEXPR format_string_compiler(basic_string_view<Char> format_str,
|
||||
PartHandler handler)
|
||||
: handler_(handler),
|
||||
format_str_(format_str),
|
||||
parse_context_(format_str) {}
|
||||
|
||||
FMT_CONSTEXPR void on_text(const Char* begin, const Char* end) {
|
||||
if (begin != end)
|
||||
handler_(part::make_text({begin, to_unsigned(end - begin)}));
|
||||
}
|
||||
|
||||
FMT_CONSTEXPR int on_arg_id() {
|
||||
part_ = part::make_arg_index(parse_context_.next_arg_id());
|
||||
return 0;
|
||||
}
|
||||
|
||||
FMT_CONSTEXPR int on_arg_id(int id) {
|
||||
parse_context_.check_arg_id(id);
|
||||
part_ = part::make_arg_index(id);
|
||||
return 0;
|
||||
}
|
||||
|
||||
FMT_CONSTEXPR int on_arg_id(basic_string_view<Char> id) {
|
||||
part_ = part::make_arg_name(id);
|
||||
return 0;
|
||||
}
|
||||
|
||||
FMT_CONSTEXPR void on_replacement_field(int, const Char* ptr) {
|
||||
part_.arg_id_end = ptr;
|
||||
handler_(part_);
|
||||
}
|
||||
|
||||
FMT_CONSTEXPR const Char* on_format_specs(int, const Char* begin,
|
||||
const Char* end) {
|
||||
auto repl = typename part::replacement();
|
||||
dynamic_specs_handler<basic_format_parse_context<Char>> handler(
|
||||
repl.specs, parse_context_);
|
||||
auto it = parse_format_specs(begin, end, handler);
|
||||
if (*it != '}') on_error("missing '}' in format string");
|
||||
repl.arg_id = part_.part_kind == part::kind::arg_index
|
||||
? arg_ref<Char>(part_.val.arg_index)
|
||||
: arg_ref<Char>(part_.val.str);
|
||||
auto part = part::make_replacement(repl);
|
||||
part.arg_id_end = begin;
|
||||
handler_(part);
|
||||
return it;
|
||||
}
|
||||
};
|
||||
|
||||
// Compiles a format string and invokes handler(part) for each parsed part.
|
||||
template <bool IS_CONSTEXPR, typename Char, typename PartHandler>
|
||||
FMT_CONSTEXPR void compile_format_string(basic_string_view<Char> format_str,
|
||||
PartHandler handler) {
|
||||
parse_format_string<IS_CONSTEXPR>(
|
||||
format_str,
|
||||
format_string_compiler<Char, PartHandler>(format_str, handler));
|
||||
}
|
||||
|
||||
template <typename OutputIt, typename Context, typename Id>
|
||||
void format_arg(
|
||||
basic_format_parse_context<typename Context::char_type>& parse_ctx,
|
||||
Context& ctx, Id arg_id) {
|
||||
ctx.advance_to(visit_format_arg(
|
||||
arg_formatter<OutputIt, typename Context::char_type>(ctx, &parse_ctx),
|
||||
ctx.arg(arg_id)));
|
||||
}
|
||||
|
||||
// vformat_to is defined in a subnamespace to prevent ADL.
|
||||
namespace cf {
|
||||
template <typename Context, typename OutputIt, typename CompiledFormat>
|
||||
auto vformat_to(OutputIt out, CompiledFormat& cf,
|
||||
basic_format_args<Context> args) -> typename Context::iterator {
|
||||
using char_type = typename Context::char_type;
|
||||
basic_format_parse_context<char_type> parse_ctx(
|
||||
to_string_view(cf.format_str_));
|
||||
Context ctx(out, args);
|
||||
|
||||
const auto& parts = cf.parts();
|
||||
for (auto part_it = std::begin(parts); part_it != std::end(parts);
|
||||
++part_it) {
|
||||
const auto& part = *part_it;
|
||||
const auto& value = part.val;
|
||||
|
||||
using format_part_t = format_part<char_type>;
|
||||
switch (part.part_kind) {
|
||||
case format_part_t::kind::text: {
|
||||
const auto text = value.str;
|
||||
auto output = ctx.out();
|
||||
auto&& it = reserve(output, text.size());
|
||||
it = std::copy_n(text.begin(), text.size(), it);
|
||||
ctx.advance_to(output);
|
||||
break;
|
||||
}
|
||||
|
||||
case format_part_t::kind::arg_index:
|
||||
advance_to(parse_ctx, part.arg_id_end);
|
||||
detail::format_arg<OutputIt>(parse_ctx, ctx, value.arg_index);
|
||||
break;
|
||||
|
||||
case format_part_t::kind::arg_name:
|
||||
advance_to(parse_ctx, part.arg_id_end);
|
||||
detail::format_arg<OutputIt>(parse_ctx, ctx, value.str);
|
||||
break;
|
||||
|
||||
case format_part_t::kind::replacement: {
|
||||
const auto& arg_id_value = value.repl.arg_id.val;
|
||||
const auto arg = value.repl.arg_id.kind == arg_id_kind::index
|
||||
? ctx.arg(arg_id_value.index)
|
||||
: ctx.arg(arg_id_value.name);
|
||||
|
||||
auto specs = value.repl.specs;
|
||||
|
||||
handle_dynamic_spec<width_checker>(specs.width, specs.width_ref, ctx);
|
||||
handle_dynamic_spec<precision_checker>(specs.precision,
|
||||
specs.precision_ref, ctx);
|
||||
|
||||
error_handler h;
|
||||
numeric_specs_checker<error_handler> checker(h, arg.type());
|
||||
if (specs.align == align::numeric) checker.require_numeric_argument();
|
||||
if (specs.sign != sign::none) checker.check_sign();
|
||||
if (specs.alt) checker.require_numeric_argument();
|
||||
if (specs.precision >= 0) checker.check_precision();
|
||||
|
||||
advance_to(parse_ctx, part.arg_id_end);
|
||||
ctx.advance_to(
|
||||
visit_format_arg(arg_formatter<OutputIt, typename Context::char_type>(
|
||||
ctx, nullptr, &specs),
|
||||
arg));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return ctx.out();
|
||||
}
|
||||
} // namespace cf
|
||||
|
||||
struct basic_compiled_format {};
|
||||
|
||||
template <typename S, typename = void>
|
||||
struct compiled_format_base : basic_compiled_format {
|
||||
using char_type = char_t<S>;
|
||||
using parts_container = std::vector<detail::format_part<char_type>>;
|
||||
|
||||
parts_container compiled_parts;
|
||||
|
||||
explicit compiled_format_base(basic_string_view<char_type> format_str) {
|
||||
compile_format_string<false>(format_str,
|
||||
[this](const format_part<char_type>& part) {
|
||||
compiled_parts.push_back(part);
|
||||
});
|
||||
}
|
||||
|
||||
const parts_container& parts() const { return compiled_parts; }
|
||||
};
|
||||
|
||||
template <typename Char, unsigned N> struct format_part_array {
|
||||
format_part<Char> data[N] = {};
|
||||
FMT_CONSTEXPR format_part_array() = default;
|
||||
};
|
||||
|
||||
template <typename Char, unsigned N>
|
||||
FMT_CONSTEXPR format_part_array<Char, N> compile_to_parts(
|
||||
basic_string_view<Char> format_str) {
|
||||
format_part_array<Char, N> parts;
|
||||
unsigned counter = 0;
|
||||
// This is not a lambda for compatibility with older compilers.
|
||||
struct {
|
||||
format_part<Char>* parts;
|
||||
unsigned* counter;
|
||||
FMT_CONSTEXPR void operator()(const format_part<Char>& part) {
|
||||
parts[(*counter)++] = part;
|
||||
}
|
||||
} collector{parts.data, &counter};
|
||||
compile_format_string<true>(format_str, collector);
|
||||
if (counter < N) {
|
||||
parts.data[counter] =
|
||||
format_part<Char>::make_text(basic_string_view<Char>());
|
||||
}
|
||||
return parts;
|
||||
}
|
||||
|
||||
template <typename T> constexpr const T& constexpr_max(const T& a, const T& b) {
|
||||
return (a < b) ? b : a;
|
||||
}
|
||||
|
||||
template <typename S>
|
||||
struct compiled_format_base<S, enable_if_t<is_compile_string<S>::value>>
|
||||
: basic_compiled_format {
|
||||
using char_type = char_t<S>;
|
||||
|
||||
FMT_CONSTEXPR explicit compiled_format_base(basic_string_view<char_type>) {}
|
||||
|
||||
// Workaround for old compilers. Format string compilation will not be
|
||||
// performed there anyway.
|
||||
#if FMT_USE_CONSTEXPR
|
||||
static FMT_CONSTEXPR_DECL const unsigned num_format_parts =
|
||||
constexpr_max(count_parts(to_string_view(S())), 1u);
|
||||
#else
|
||||
static const unsigned num_format_parts = 1;
|
||||
#endif
|
||||
|
||||
using parts_container = format_part<char_type>[num_format_parts];
|
||||
|
||||
const parts_container& parts() const {
|
||||
static FMT_CONSTEXPR_DECL const auto compiled_parts =
|
||||
compile_to_parts<char_type, num_format_parts>(
|
||||
detail::to_string_view(S()));
|
||||
return compiled_parts.data;
|
||||
}
|
||||
};
|
||||
|
||||
template <typename S, typename... Args>
|
||||
class compiled_format : private compiled_format_base<S> {
|
||||
public:
|
||||
using typename compiled_format_base<S>::char_type;
|
||||
|
||||
private:
|
||||
basic_string_view<char_type> format_str_;
|
||||
|
||||
template <typename Context, typename OutputIt, typename CompiledFormat>
|
||||
friend auto cf::vformat_to(OutputIt out, CompiledFormat& cf,
|
||||
basic_format_args<Context> args) ->
|
||||
typename Context::iterator;
|
||||
|
||||
public:
|
||||
compiled_format() = delete;
|
||||
explicit constexpr compiled_format(basic_string_view<char_type> format_str)
|
||||
: compiled_format_base<S>(format_str), format_str_(format_str) {}
|
||||
};
|
||||
|
||||
#ifdef __cpp_if_constexpr
|
||||
template <typename... Args> struct type_list {};
|
||||
|
||||
// Returns a reference to the argument at index N from [first, rest...].
|
||||
template <int N, typename T, typename... Args>
|
||||
constexpr const auto& get(const T& first, const Args&... rest) {
|
||||
static_assert(N < 1 + sizeof...(Args), "index is out of bounds");
|
||||
if constexpr (N == 0)
|
||||
return first;
|
||||
else
|
||||
return get<N - 1>(rest...);
|
||||
}
|
||||
|
||||
template <int N, typename> struct get_type_impl;
|
||||
|
||||
template <int N, typename... Args> struct get_type_impl<N, type_list<Args...>> {
|
||||
using type = remove_cvref_t<decltype(get<N>(std::declval<Args>()...))>;
|
||||
};
|
||||
|
||||
template <int N, typename T>
|
||||
using get_type = typename get_type_impl<N, T>::type;
|
||||
|
||||
template <typename T> struct is_compiled_format : std::false_type {};
|
||||
|
||||
template <typename Char> struct text {
|
||||
basic_string_view<Char> data;
|
||||
using char_type = Char;
|
||||
|
||||
template <typename OutputIt, typename... Args>
|
||||
OutputIt format(OutputIt out, const Args&...) const {
|
||||
return write<Char>(out, data);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Char>
|
||||
struct is_compiled_format<text<Char>> : std::true_type {};
|
||||
|
||||
template <typename Char>
|
||||
constexpr text<Char> make_text(basic_string_view<Char> s, size_t pos,
|
||||
size_t size) {
|
||||
return {{&s[pos], size}};
|
||||
}
|
||||
|
||||
// A replacement field that refers to argument N.
|
||||
template <typename Char, typename T, int N> struct field {
|
||||
using char_type = Char;
|
||||
|
||||
template <typename OutputIt, typename... Args>
|
||||
OutputIt format(OutputIt out, const Args&... args) const {
|
||||
// This ensures that the argument type is convertile to `const T&`.
|
||||
const T& arg = get<N>(args...);
|
||||
return write<Char>(out, arg);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Char, typename T, int N>
|
||||
struct is_compiled_format<field<Char, T, N>> : std::true_type {};
|
||||
|
||||
// A replacement field that refers to argument N and has format specifiers.
|
||||
template <typename Char, typename T, int N> struct spec_field {
|
||||
using char_type = Char;
|
||||
mutable formatter<T, Char> fmt;
|
||||
|
||||
template <typename OutputIt, typename... Args>
|
||||
OutputIt format(OutputIt out, const Args&... args) const {
|
||||
// This ensures that the argument type is convertile to `const T&`.
|
||||
const T& arg = get<N>(args...);
|
||||
basic_format_context<OutputIt, Char> ctx(out, {});
|
||||
return fmt.format(arg, ctx);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Char, typename T, int N>
|
||||
struct is_compiled_format<spec_field<Char, T, N>> : std::true_type {};
|
||||
|
||||
template <typename L, typename R> struct concat {
|
||||
L lhs;
|
||||
R rhs;
|
||||
using char_type = typename L::char_type;
|
||||
|
||||
template <typename OutputIt, typename... Args>
|
||||
OutputIt format(OutputIt out, const Args&... args) const {
|
||||
out = lhs.format(out, args...);
|
||||
return rhs.format(out, args...);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename L, typename R>
|
||||
struct is_compiled_format<concat<L, R>> : std::true_type {};
|
||||
|
||||
template <typename L, typename R>
|
||||
constexpr concat<L, R> make_concat(L lhs, R rhs) {
|
||||
return {lhs, rhs};
|
||||
}
|
||||
|
||||
struct unknown_format {};
|
||||
|
||||
template <typename Char>
|
||||
constexpr size_t parse_text(basic_string_view<Char> str, size_t pos) {
|
||||
for (size_t size = str.size(); pos != size; ++pos) {
|
||||
if (str[pos] == '{' || str[pos] == '}') break;
|
||||
}
|
||||
return pos;
|
||||
}
|
||||
|
||||
template <typename Args, size_t POS, int ID, typename S>
|
||||
constexpr auto compile_format_string(S format_str);
|
||||
|
||||
template <typename Args, size_t POS, int ID, typename T, typename S>
|
||||
constexpr auto parse_tail(T head, S format_str) {
|
||||
if constexpr (POS !=
|
||||
basic_string_view<typename S::char_type>(format_str).size()) {
|
||||
constexpr auto tail = compile_format_string<Args, POS, ID>(format_str);
|
||||
if constexpr (std::is_same<remove_cvref_t<decltype(tail)>,
|
||||
unknown_format>())
|
||||
return tail;
|
||||
else
|
||||
return make_concat(head, tail);
|
||||
} else {
|
||||
return head;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T, typename Char> struct parse_specs_result {
|
||||
formatter<T, Char> fmt;
|
||||
size_t end;
|
||||
};
|
||||
|
||||
template <typename T, typename Char>
|
||||
constexpr parse_specs_result<T, Char> parse_specs(basic_string_view<Char> str,
|
||||
size_t pos) {
|
||||
str.remove_prefix(pos);
|
||||
auto ctx = basic_format_parse_context<Char>(str);
|
||||
auto f = formatter<T, Char>();
|
||||
auto end = f.parse(ctx);
|
||||
return {f, pos + (end - str.data()) + 1};
|
||||
}
|
||||
|
||||
// Compiles a non-empty format string and returns the compiled representation
|
||||
// or unknown_format() on unrecognized input.
|
||||
template <typename Args, size_t POS, int ID, typename S>
|
||||
constexpr auto compile_format_string(S format_str) {
|
||||
using char_type = typename S::char_type;
|
||||
constexpr basic_string_view<char_type> str = format_str;
|
||||
if constexpr (str[POS] == '{') {
|
||||
if (POS + 1 == str.size())
|
||||
throw format_error("unmatched '{' in format string");
|
||||
if constexpr (str[POS + 1] == '{') {
|
||||
return parse_tail<Args, POS + 2, ID>(make_text(str, POS, 1), format_str);
|
||||
} else if constexpr (str[POS + 1] == '}') {
|
||||
using type = get_type<ID, Args>;
|
||||
return parse_tail<Args, POS + 2, ID + 1>(field<char_type, type, ID>(),
|
||||
format_str);
|
||||
} else if constexpr (str[POS + 1] == ':') {
|
||||
using type = get_type<ID, Args>;
|
||||
constexpr auto result = parse_specs<type>(str, POS + 2);
|
||||
return parse_tail<Args, result.end, ID + 1>(
|
||||
spec_field<char_type, type, ID>{result.fmt}, format_str);
|
||||
} else {
|
||||
return unknown_format();
|
||||
}
|
||||
} else if constexpr (str[POS] == '}') {
|
||||
if (POS + 1 == str.size())
|
||||
throw format_error("unmatched '}' in format string");
|
||||
return parse_tail<Args, POS + 2, ID>(make_text(str, POS, 1), format_str);
|
||||
} else {
|
||||
constexpr auto end = parse_text(str, POS + 1);
|
||||
return parse_tail<Args, end, ID>(make_text(str, POS, end - POS),
|
||||
format_str);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename... Args, typename S,
|
||||
FMT_ENABLE_IF(is_compile_string<S>::value ||
|
||||
detail::is_compiled_string<S>::value)>
|
||||
constexpr auto compile(S format_str) {
|
||||
constexpr basic_string_view<typename S::char_type> str = format_str;
|
||||
if constexpr (str.size() == 0) {
|
||||
return detail::make_text(str, 0, 0);
|
||||
} else {
|
||||
constexpr auto result =
|
||||
detail::compile_format_string<detail::type_list<Args...>, 0, 0>(
|
||||
format_str);
|
||||
if constexpr (std::is_same<remove_cvref_t<decltype(result)>,
|
||||
detail::unknown_format>()) {
|
||||
return detail::compiled_format<S, Args...>(to_string_view(format_str));
|
||||
} else {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
#else
|
||||
template <typename... Args, typename S,
|
||||
FMT_ENABLE_IF(is_compile_string<S>::value)>
|
||||
constexpr auto compile(S format_str) -> detail::compiled_format<S, Args...> {
|
||||
return detail::compiled_format<S, Args...>(to_string_view(format_str));
|
||||
}
|
||||
#endif // __cpp_if_constexpr
|
||||
|
||||
// Compiles the format string which must be a string literal.
|
||||
template <typename... Args, typename Char, size_t N>
|
||||
auto compile(const Char (&format_str)[N])
|
||||
-> detail::compiled_format<const Char*, Args...> {
|
||||
return detail::compiled_format<const Char*, Args...>(
|
||||
basic_string_view<Char>(format_str, N - 1));
|
||||
}
|
||||
} // namespace detail
|
||||
|
||||
// DEPRECATED! use FMT_COMPILE instead.
|
||||
template <typename... Args>
|
||||
FMT_DEPRECATED auto compile(const Args&... args)
|
||||
-> decltype(detail::compile(args...)) {
|
||||
return detail::compile(args...);
|
||||
}
|
||||
|
||||
#if FMT_USE_CONSTEXPR
|
||||
# ifdef __cpp_if_constexpr
|
||||
|
||||
template <typename CompiledFormat, typename... Args,
|
||||
typename Char = typename CompiledFormat::char_type,
|
||||
FMT_ENABLE_IF(detail::is_compiled_format<CompiledFormat>::value)>
|
||||
FMT_INLINE std::basic_string<Char> format(const CompiledFormat& cf,
|
||||
const Args&... args) {
|
||||
basic_memory_buffer<Char> buffer;
|
||||
detail::buffer<Char>& base = buffer;
|
||||
cf.format(std::back_inserter(base), args...);
|
||||
return to_string(buffer);
|
||||
}
|
||||
|
||||
template <typename OutputIt, typename CompiledFormat, typename... Args,
|
||||
FMT_ENABLE_IF(detail::is_compiled_format<CompiledFormat>::value)>
|
||||
OutputIt format_to(OutputIt out, const CompiledFormat& cf,
|
||||
const Args&... args) {
|
||||
return cf.format(out, args...);
|
||||
}
|
||||
# endif // __cpp_if_constexpr
|
||||
#endif // FMT_USE_CONSTEXPR
|
||||
|
||||
template <typename CompiledFormat, typename... Args,
|
||||
typename Char = typename CompiledFormat::char_type,
|
||||
FMT_ENABLE_IF(std::is_base_of<detail::basic_compiled_format,
|
||||
CompiledFormat>::value)>
|
||||
std::basic_string<Char> format(const CompiledFormat& cf, const Args&... args) {
|
||||
basic_memory_buffer<Char> buffer;
|
||||
using context = buffer_context<Char>;
|
||||
detail::buffer<Char>& base = buffer;
|
||||
detail::cf::vformat_to<context>(std::back_inserter(base), cf,
|
||||
make_format_args<context>(args...));
|
||||
return to_string(buffer);
|
||||
}
|
||||
|
||||
template <typename S, typename... Args,
|
||||
FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>
|
||||
FMT_INLINE std::basic_string<typename S::char_type> format(const S&,
|
||||
Args&&... args) {
|
||||
constexpr basic_string_view<typename S::char_type> str = S();
|
||||
if (str.size() == 2 && str[0] == '{' && str[1] == '}')
|
||||
return fmt::to_string(detail::first(args...));
|
||||
constexpr auto compiled = detail::compile<Args...>(S());
|
||||
return format(compiled, std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
template <typename OutputIt, typename CompiledFormat, typename... Args,
|
||||
FMT_ENABLE_IF(std::is_base_of<detail::basic_compiled_format,
|
||||
CompiledFormat>::value)>
|
||||
OutputIt format_to(OutputIt out, const CompiledFormat& cf,
|
||||
const Args&... args) {
|
||||
using char_type = typename CompiledFormat::char_type;
|
||||
using context = format_context_t<OutputIt, char_type>;
|
||||
return detail::cf::vformat_to<context>(out, cf,
|
||||
make_format_args<context>(args...));
|
||||
}
|
||||
|
||||
template <typename OutputIt, typename S, typename... Args,
|
||||
FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>
|
||||
OutputIt format_to(OutputIt out, const S&, const Args&... args) {
|
||||
constexpr auto compiled = detail::compile<Args...>(S());
|
||||
return format_to(out, compiled, args...);
|
||||
}
|
||||
|
||||
template <
|
||||
typename OutputIt, typename CompiledFormat, typename... Args,
|
||||
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt>::value&& std::is_base_of<
|
||||
detail::basic_compiled_format, CompiledFormat>::value)>
|
||||
format_to_n_result<OutputIt> format_to_n(OutputIt out, size_t n,
|
||||
const CompiledFormat& cf,
|
||||
const Args&... args) {
|
||||
auto it =
|
||||
format_to(detail::truncating_iterator<OutputIt>(out, n), cf, args...);
|
||||
return {it.base(), it.count()};
|
||||
}
|
||||
|
||||
template <typename CompiledFormat, typename... Args>
|
||||
size_t formatted_size(const CompiledFormat& cf, const Args&... args) {
|
||||
return format_to(detail::counting_iterator(), cf, args...).count();
|
||||
}
|
||||
|
||||
FMT_END_NAMESPACE
|
||||
|
||||
#endif // FMT_COMPILE_H_
|
||||
1882
external/fmtlib/include/fmt/core.h
vendored
Normal file
1882
external/fmtlib/include/fmt/core.h
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1453
external/fmtlib/include/fmt/format-inl.h
vendored
Normal file
1453
external/fmtlib/include/fmt/format-inl.h
vendored
Normal file
File diff suppressed because it is too large
Load Diff
3729
external/fmtlib/include/fmt/format.h
vendored
Normal file
3729
external/fmtlib/include/fmt/format.h
vendored
Normal file
File diff suppressed because it is too large
Load Diff
78
external/fmtlib/include/fmt/locale.h
vendored
Normal file
78
external/fmtlib/include/fmt/locale.h
vendored
Normal file
@@ -0,0 +1,78 @@
|
||||
// Formatting library for C++ - std::locale support
|
||||
//
|
||||
// Copyright (c) 2012 - present, Victor Zverovich
|
||||
// All rights reserved.
|
||||
//
|
||||
// For the license information refer to format.h.
|
||||
|
||||
#ifndef FMT_LOCALE_H_
|
||||
#define FMT_LOCALE_H_
|
||||
|
||||
#include <locale>
|
||||
|
||||
#include "format.h"
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
|
||||
namespace detail {
|
||||
template <typename Char>
|
||||
typename buffer_context<Char>::iterator vformat_to(
|
||||
const std::locale& loc, buffer<Char>& buf,
|
||||
basic_string_view<Char> format_str,
|
||||
basic_format_args<buffer_context<type_identity_t<Char>>> args) {
|
||||
using af = arg_formatter<typename buffer_context<Char>::iterator, Char>;
|
||||
return vformat_to<af>(std::back_inserter(buf), to_string_view(format_str),
|
||||
args, detail::locale_ref(loc));
|
||||
}
|
||||
|
||||
template <typename Char>
|
||||
std::basic_string<Char> vformat(
|
||||
const std::locale& loc, basic_string_view<Char> format_str,
|
||||
basic_format_args<buffer_context<type_identity_t<Char>>> args) {
|
||||
basic_memory_buffer<Char> buffer;
|
||||
detail::vformat_to(loc, buffer, format_str, args);
|
||||
return fmt::to_string(buffer);
|
||||
}
|
||||
} // namespace detail
|
||||
|
||||
template <typename S, typename Char = char_t<S>>
|
||||
inline std::basic_string<Char> vformat(
|
||||
const std::locale& loc, const S& format_str,
|
||||
basic_format_args<buffer_context<type_identity_t<Char>>> args) {
|
||||
return detail::vformat(loc, to_string_view(format_str), args);
|
||||
}
|
||||
|
||||
template <typename S, typename... Args, typename Char = char_t<S>>
|
||||
inline std::basic_string<Char> format(const std::locale& loc,
|
||||
const S& format_str, Args&&... args) {
|
||||
return detail::vformat(
|
||||
loc, to_string_view(format_str),
|
||||
detail::make_args_checked<Args...>(format_str, args...));
|
||||
}
|
||||
|
||||
template <typename S, typename OutputIt, typename... Args,
|
||||
typename Char = enable_if_t<
|
||||
detail::is_output_iterator<OutputIt>::value, char_t<S>>>
|
||||
inline OutputIt vformat_to(
|
||||
OutputIt out, const std::locale& loc, const S& format_str,
|
||||
format_args_t<type_identity_t<OutputIt>, Char> args) {
|
||||
using af = detail::arg_formatter<OutputIt, Char>;
|
||||
return vformat_to<af>(out, to_string_view(format_str), args,
|
||||
detail::locale_ref(loc));
|
||||
}
|
||||
|
||||
template <typename OutputIt, typename S, typename... Args,
|
||||
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt>::value&&
|
||||
detail::is_string<S>::value)>
|
||||
inline OutputIt format_to(OutputIt out, const std::locale& loc,
|
||||
const S& format_str, Args&&... args) {
|
||||
detail::check_format_string<Args...>(format_str);
|
||||
using context = format_context_t<OutputIt, char_t<S>>;
|
||||
format_arg_store<context, Args...> as{args...};
|
||||
return vformat_to(out, loc, to_string_view(format_str),
|
||||
basic_format_args<context>(as));
|
||||
}
|
||||
|
||||
FMT_END_NAMESPACE
|
||||
|
||||
#endif // FMT_LOCALE_H_
|
||||
450
external/fmtlib/include/fmt/os.h
vendored
Normal file
450
external/fmtlib/include/fmt/os.h
vendored
Normal file
@@ -0,0 +1,450 @@
|
||||
// Formatting library for C++ - optional OS-specific functionality
|
||||
//
|
||||
// Copyright (c) 2012 - present, Victor Zverovich
|
||||
// All rights reserved.
|
||||
//
|
||||
// For the license information refer to format.h.
|
||||
|
||||
#ifndef FMT_OS_H_
|
||||
#define FMT_OS_H_
|
||||
|
||||
#if defined(__MINGW32__) || defined(__CYGWIN__)
|
||||
// Workaround MinGW bug https://sourceforge.net/p/mingw/bugs/2024/.
|
||||
# undef __STRICT_ANSI__
|
||||
#endif
|
||||
|
||||
#include <cerrno>
|
||||
#include <clocale> // for locale_t
|
||||
#include <cstddef>
|
||||
#include <cstdio>
|
||||
#include <cstdlib> // for strtod_l
|
||||
|
||||
#if defined __APPLE__ || defined(__FreeBSD__)
|
||||
# include <xlocale.h> // for LC_NUMERIC_MASK on OS X
|
||||
#endif
|
||||
|
||||
#include "format.h"
|
||||
|
||||
// UWP doesn't provide _pipe.
|
||||
#if FMT_HAS_INCLUDE("winapifamily.h")
|
||||
# include <winapifamily.h>
|
||||
#endif
|
||||
#if FMT_HAS_INCLUDE("fcntl.h") && \
|
||||
(!defined(WINAPI_FAMILY) || (WINAPI_FAMILY == WINAPI_FAMILY_DESKTOP_APP))
|
||||
# include <fcntl.h> // for O_RDONLY
|
||||
# define FMT_USE_FCNTL 1
|
||||
#else
|
||||
# define FMT_USE_FCNTL 0
|
||||
#endif
|
||||
|
||||
#ifndef FMT_POSIX
|
||||
# if defined(_WIN32) && !defined(__MINGW32__)
|
||||
// Fix warnings about deprecated symbols.
|
||||
# define FMT_POSIX(call) _##call
|
||||
# else
|
||||
# define FMT_POSIX(call) call
|
||||
# endif
|
||||
#endif
|
||||
|
||||
// Calls to system functions are wrapped in FMT_SYSTEM for testability.
|
||||
#ifdef FMT_SYSTEM
|
||||
# define FMT_POSIX_CALL(call) FMT_SYSTEM(call)
|
||||
#else
|
||||
# define FMT_SYSTEM(call) ::call
|
||||
# ifdef _WIN32
|
||||
// Fix warnings about deprecated symbols.
|
||||
# define FMT_POSIX_CALL(call) ::_##call
|
||||
# else
|
||||
# define FMT_POSIX_CALL(call) ::call
|
||||
# endif
|
||||
#endif
|
||||
|
||||
// Retries the expression while it evaluates to error_result and errno
|
||||
// equals to EINTR.
|
||||
#ifndef _WIN32
|
||||
# define FMT_RETRY_VAL(result, expression, error_result) \
|
||||
do { \
|
||||
(result) = (expression); \
|
||||
} while ((result) == (error_result) && errno == EINTR)
|
||||
#else
|
||||
# define FMT_RETRY_VAL(result, expression, error_result) result = (expression)
|
||||
#endif
|
||||
|
||||
#define FMT_RETRY(result, expression) FMT_RETRY_VAL(result, expression, -1)
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
|
||||
/**
|
||||
\rst
|
||||
A reference to a null-terminated string. It can be constructed from a C
|
||||
string or ``std::string``.
|
||||
|
||||
You can use one of the following type aliases for common character types:
|
||||
|
||||
+---------------+-----------------------------+
|
||||
| Type | Definition |
|
||||
+===============+=============================+
|
||||
| cstring_view | basic_cstring_view<char> |
|
||||
+---------------+-----------------------------+
|
||||
| wcstring_view | basic_cstring_view<wchar_t> |
|
||||
+---------------+-----------------------------+
|
||||
|
||||
This class is most useful as a parameter type to allow passing
|
||||
different types of strings to a function, for example::
|
||||
|
||||
template <typename... Args>
|
||||
std::string format(cstring_view format_str, const Args & ... args);
|
||||
|
||||
format("{}", 42);
|
||||
format(std::string("{}"), 42);
|
||||
\endrst
|
||||
*/
|
||||
template <typename Char> class basic_cstring_view {
|
||||
private:
|
||||
const Char* data_;
|
||||
|
||||
public:
|
||||
/** Constructs a string reference object from a C string. */
|
||||
basic_cstring_view(const Char* s) : data_(s) {}
|
||||
|
||||
/**
|
||||
\rst
|
||||
Constructs a string reference from an ``std::string`` object.
|
||||
\endrst
|
||||
*/
|
||||
basic_cstring_view(const std::basic_string<Char>& s) : data_(s.c_str()) {}
|
||||
|
||||
/** Returns the pointer to a C string. */
|
||||
const Char* c_str() const { return data_; }
|
||||
};
|
||||
|
||||
using cstring_view = basic_cstring_view<char>;
|
||||
using wcstring_view = basic_cstring_view<wchar_t>;
|
||||
|
||||
// An error code.
|
||||
class error_code {
|
||||
private:
|
||||
int value_;
|
||||
|
||||
public:
|
||||
explicit error_code(int value = 0) FMT_NOEXCEPT : value_(value) {}
|
||||
|
||||
int get() const FMT_NOEXCEPT { return value_; }
|
||||
};
|
||||
|
||||
#ifdef _WIN32
|
||||
namespace detail {
|
||||
// A converter from UTF-16 to UTF-8.
|
||||
// It is only provided for Windows since other systems support UTF-8 natively.
|
||||
class utf16_to_utf8 {
|
||||
private:
|
||||
memory_buffer buffer_;
|
||||
|
||||
public:
|
||||
utf16_to_utf8() {}
|
||||
FMT_API explicit utf16_to_utf8(wstring_view s);
|
||||
operator string_view() const { return string_view(&buffer_[0], size()); }
|
||||
size_t size() const { return buffer_.size() - 1; }
|
||||
const char* c_str() const { return &buffer_[0]; }
|
||||
std::string str() const { return std::string(&buffer_[0], size()); }
|
||||
|
||||
// Performs conversion returning a system error code instead of
|
||||
// throwing exception on conversion error. This method may still throw
|
||||
// in case of memory allocation error.
|
||||
FMT_API int convert(wstring_view s);
|
||||
};
|
||||
|
||||
FMT_API void format_windows_error(buffer<char>& out, int error_code,
|
||||
string_view message) FMT_NOEXCEPT;
|
||||
} // namespace detail
|
||||
|
||||
/** A Windows error. */
|
||||
class windows_error : public system_error {
|
||||
private:
|
||||
FMT_API void init(int error_code, string_view format_str, format_args args);
|
||||
|
||||
public:
|
||||
/**
|
||||
\rst
|
||||
Constructs a :class:`fmt::windows_error` object with the description
|
||||
of the form
|
||||
|
||||
.. parsed-literal::
|
||||
*<message>*: *<system-message>*
|
||||
|
||||
where *<message>* is the formatted message and *<system-message>* is the
|
||||
system message corresponding to the error code.
|
||||
*error_code* is a Windows error code as given by ``GetLastError``.
|
||||
If *error_code* is not a valid error code such as -1, the system message
|
||||
will look like "error -1".
|
||||
|
||||
**Example**::
|
||||
|
||||
// This throws a windows_error with the description
|
||||
// cannot open file 'madeup': The system cannot find the file specified.
|
||||
// or similar (system message may vary).
|
||||
const char *filename = "madeup";
|
||||
LPOFSTRUCT of = LPOFSTRUCT();
|
||||
HFILE file = OpenFile(filename, &of, OF_READ);
|
||||
if (file == HFILE_ERROR) {
|
||||
throw fmt::windows_error(GetLastError(),
|
||||
"cannot open file '{}'", filename);
|
||||
}
|
||||
\endrst
|
||||
*/
|
||||
template <typename... Args>
|
||||
windows_error(int error_code, string_view message, const Args&... args) {
|
||||
init(error_code, message, make_format_args(args...));
|
||||
}
|
||||
};
|
||||
|
||||
// Reports a Windows error without throwing an exception.
|
||||
// Can be used to report errors from destructors.
|
||||
FMT_API void report_windows_error(int error_code,
|
||||
string_view message) FMT_NOEXCEPT;
|
||||
#endif // _WIN32
|
||||
|
||||
// A buffered file.
|
||||
class buffered_file {
|
||||
private:
|
||||
FILE* file_;
|
||||
|
||||
friend class file;
|
||||
|
||||
explicit buffered_file(FILE* f) : file_(f) {}
|
||||
|
||||
public:
|
||||
buffered_file(const buffered_file&) = delete;
|
||||
void operator=(const buffered_file&) = delete;
|
||||
|
||||
// Constructs a buffered_file object which doesn't represent any file.
|
||||
buffered_file() FMT_NOEXCEPT : file_(nullptr) {}
|
||||
|
||||
// Destroys the object closing the file it represents if any.
|
||||
FMT_API ~buffered_file() FMT_NOEXCEPT;
|
||||
|
||||
public:
|
||||
buffered_file(buffered_file&& other) FMT_NOEXCEPT : file_(other.file_) {
|
||||
other.file_ = nullptr;
|
||||
}
|
||||
|
||||
buffered_file& operator=(buffered_file&& other) {
|
||||
close();
|
||||
file_ = other.file_;
|
||||
other.file_ = nullptr;
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Opens a file.
|
||||
FMT_API buffered_file(cstring_view filename, cstring_view mode);
|
||||
|
||||
// Closes the file.
|
||||
FMT_API void close();
|
||||
|
||||
// Returns the pointer to a FILE object representing this file.
|
||||
FILE* get() const FMT_NOEXCEPT { return file_; }
|
||||
|
||||
// We place parentheses around fileno to workaround a bug in some versions
|
||||
// of MinGW that define fileno as a macro.
|
||||
FMT_API int(fileno)() const;
|
||||
|
||||
void vprint(string_view format_str, format_args args) {
|
||||
fmt::vprint(file_, format_str, args);
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
inline void print(string_view format_str, const Args&... args) {
|
||||
vprint(format_str, make_format_args(args...));
|
||||
}
|
||||
};
|
||||
|
||||
#if FMT_USE_FCNTL
|
||||
// A file. Closed file is represented by a file object with descriptor -1.
|
||||
// Methods that are not declared with FMT_NOEXCEPT may throw
|
||||
// fmt::system_error in case of failure. Note that some errors such as
|
||||
// closing the file multiple times will cause a crash on Windows rather
|
||||
// than an exception. You can get standard behavior by overriding the
|
||||
// invalid parameter handler with _set_invalid_parameter_handler.
|
||||
class file {
|
||||
private:
|
||||
int fd_; // File descriptor.
|
||||
|
||||
// Constructs a file object with a given descriptor.
|
||||
explicit file(int fd) : fd_(fd) {}
|
||||
|
||||
public:
|
||||
// Possible values for the oflag argument to the constructor.
|
||||
enum {
|
||||
RDONLY = FMT_POSIX(O_RDONLY), // Open for reading only.
|
||||
WRONLY = FMT_POSIX(O_WRONLY), // Open for writing only.
|
||||
RDWR = FMT_POSIX(O_RDWR), // Open for reading and writing.
|
||||
CREATE = FMT_POSIX(O_CREAT) // Create if the file doesn't exist.
|
||||
};
|
||||
|
||||
// Constructs a file object which doesn't represent any file.
|
||||
file() FMT_NOEXCEPT : fd_(-1) {}
|
||||
|
||||
// Opens a file and constructs a file object representing this file.
|
||||
FMT_API file(cstring_view path, int oflag);
|
||||
|
||||
public:
|
||||
file(const file&) = delete;
|
||||
void operator=(const file&) = delete;
|
||||
|
||||
file(file&& other) FMT_NOEXCEPT : fd_(other.fd_) { other.fd_ = -1; }
|
||||
|
||||
file& operator=(file&& other) FMT_NOEXCEPT {
|
||||
close();
|
||||
fd_ = other.fd_;
|
||||
other.fd_ = -1;
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Destroys the object closing the file it represents if any.
|
||||
FMT_API ~file() FMT_NOEXCEPT;
|
||||
|
||||
// Returns the file descriptor.
|
||||
int descriptor() const FMT_NOEXCEPT { return fd_; }
|
||||
|
||||
// Closes the file.
|
||||
FMT_API void close();
|
||||
|
||||
// Returns the file size. The size has signed type for consistency with
|
||||
// stat::st_size.
|
||||
FMT_API long long size() const;
|
||||
|
||||
// Attempts to read count bytes from the file into the specified buffer.
|
||||
FMT_API size_t read(void* buffer, size_t count);
|
||||
|
||||
// Attempts to write count bytes from the specified buffer to the file.
|
||||
FMT_API size_t write(const void* buffer, size_t count);
|
||||
|
||||
// Duplicates a file descriptor with the dup function and returns
|
||||
// the duplicate as a file object.
|
||||
FMT_API static file dup(int fd);
|
||||
|
||||
// Makes fd be the copy of this file descriptor, closing fd first if
|
||||
// necessary.
|
||||
FMT_API void dup2(int fd);
|
||||
|
||||
// Makes fd be the copy of this file descriptor, closing fd first if
|
||||
// necessary.
|
||||
FMT_API void dup2(int fd, error_code& ec) FMT_NOEXCEPT;
|
||||
|
||||
// Creates a pipe setting up read_end and write_end file objects for reading
|
||||
// and writing respectively.
|
||||
FMT_API static void pipe(file& read_end, file& write_end);
|
||||
|
||||
// Creates a buffered_file object associated with this file and detaches
|
||||
// this file object from the file.
|
||||
FMT_API buffered_file fdopen(const char* mode);
|
||||
};
|
||||
|
||||
// Returns the memory page size.
|
||||
long getpagesize();
|
||||
|
||||
class direct_buffered_file;
|
||||
|
||||
template <typename S, typename... Args>
|
||||
void print(direct_buffered_file& f, const S& format_str,
|
||||
const Args&... args);
|
||||
|
||||
// A buffered file with a direct buffer access and no synchronization.
|
||||
class direct_buffered_file {
|
||||
private:
|
||||
file file_;
|
||||
|
||||
enum { buffer_size = 4096 };
|
||||
char buffer_[buffer_size];
|
||||
int pos_;
|
||||
|
||||
void flush() {
|
||||
if (pos_ == 0) return;
|
||||
file_.write(buffer_, pos_);
|
||||
pos_ = 0;
|
||||
}
|
||||
|
||||
int free_capacity() const { return buffer_size - pos_; }
|
||||
|
||||
public:
|
||||
direct_buffered_file(cstring_view path, int oflag)
|
||||
: file_(path, oflag), pos_(0) {}
|
||||
|
||||
~direct_buffered_file() {
|
||||
flush();
|
||||
}
|
||||
|
||||
void close() {
|
||||
flush();
|
||||
file_.close();
|
||||
}
|
||||
|
||||
template <typename S, typename... Args>
|
||||
friend void print(direct_buffered_file& f, const S& format_str,
|
||||
const Args&... args) {
|
||||
// We could avoid double buffering.
|
||||
auto buf = fmt::memory_buffer();
|
||||
fmt::format_to(std::back_inserter(buf), format_str, args...);
|
||||
auto remaining_pos = 0;
|
||||
auto remaining_size = buf.size();
|
||||
while (remaining_size > detail::to_unsigned(f.free_capacity())) {
|
||||
auto size = f.free_capacity();
|
||||
memcpy(f.buffer_ + f.pos_, buf.data() + remaining_pos, size);
|
||||
f.pos_ += size;
|
||||
f.flush();
|
||||
remaining_pos += size;
|
||||
remaining_size -= size;
|
||||
}
|
||||
memcpy(f.buffer_ + f.pos_, buf.data() + remaining_pos, remaining_size);
|
||||
f.pos_ += static_cast<int>(remaining_size);
|
||||
}
|
||||
};
|
||||
#endif // FMT_USE_FCNTL
|
||||
|
||||
#ifdef FMT_LOCALE
|
||||
// A "C" numeric locale.
|
||||
class locale {
|
||||
private:
|
||||
# ifdef _WIN32
|
||||
using locale_t = _locale_t;
|
||||
|
||||
static void freelocale(locale_t loc) { _free_locale(loc); }
|
||||
|
||||
static double strtod_l(const char* nptr, char** endptr, _locale_t loc) {
|
||||
return _strtod_l(nptr, endptr, loc);
|
||||
}
|
||||
# endif
|
||||
|
||||
locale_t locale_;
|
||||
|
||||
public:
|
||||
using type = locale_t;
|
||||
locale(const locale&) = delete;
|
||||
void operator=(const locale&) = delete;
|
||||
|
||||
locale() {
|
||||
# ifndef _WIN32
|
||||
locale_ = FMT_SYSTEM(newlocale(LC_NUMERIC_MASK, "C", nullptr));
|
||||
# else
|
||||
locale_ = _create_locale(LC_NUMERIC, "C");
|
||||
# endif
|
||||
if (!locale_) FMT_THROW(system_error(errno, "cannot create locale"));
|
||||
}
|
||||
~locale() { freelocale(locale_); }
|
||||
|
||||
type get() const { return locale_; }
|
||||
|
||||
// Converts string to floating-point number and advances str past the end
|
||||
// of the parsed input.
|
||||
double strtod(const char*& str) const {
|
||||
char* end = nullptr;
|
||||
double result = strtod_l(str, &end, locale_);
|
||||
str = end;
|
||||
return result;
|
||||
}
|
||||
};
|
||||
using Locale FMT_DEPRECATED_ALIAS = locale;
|
||||
#endif // FMT_LOCALE
|
||||
FMT_END_NAMESPACE
|
||||
|
||||
#endif // FMT_OS_H_
|
||||
167
external/fmtlib/include/fmt/ostream.h
vendored
Normal file
167
external/fmtlib/include/fmt/ostream.h
vendored
Normal file
@@ -0,0 +1,167 @@
|
||||
// Formatting library for C++ - std::ostream support
|
||||
//
|
||||
// Copyright (c) 2012 - present, Victor Zverovich
|
||||
// All rights reserved.
|
||||
//
|
||||
// For the license information refer to format.h.
|
||||
|
||||
#ifndef FMT_OSTREAM_H_
|
||||
#define FMT_OSTREAM_H_
|
||||
|
||||
#include <ostream>
|
||||
|
||||
#include "format.h"
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
|
||||
template <typename Char> class basic_printf_parse_context;
|
||||
template <typename OutputIt, typename Char> class basic_printf_context;
|
||||
|
||||
namespace detail {
|
||||
|
||||
template <class Char> class formatbuf : public std::basic_streambuf<Char> {
|
||||
private:
|
||||
using int_type = typename std::basic_streambuf<Char>::int_type;
|
||||
using traits_type = typename std::basic_streambuf<Char>::traits_type;
|
||||
|
||||
buffer<Char>& buffer_;
|
||||
|
||||
public:
|
||||
formatbuf(buffer<Char>& buf) : buffer_(buf) {}
|
||||
|
||||
protected:
|
||||
// The put-area is actually always empty. This makes the implementation
|
||||
// simpler and has the advantage that the streambuf and the buffer are always
|
||||
// in sync and sputc never writes into uninitialized memory. The obvious
|
||||
// disadvantage is that each call to sputc always results in a (virtual) call
|
||||
// to overflow. There is no disadvantage here for sputn since this always
|
||||
// results in a call to xsputn.
|
||||
|
||||
int_type overflow(int_type ch = traits_type::eof()) FMT_OVERRIDE {
|
||||
if (!traits_type::eq_int_type(ch, traits_type::eof()))
|
||||
buffer_.push_back(static_cast<Char>(ch));
|
||||
return ch;
|
||||
}
|
||||
|
||||
std::streamsize xsputn(const Char* s, std::streamsize count) FMT_OVERRIDE {
|
||||
buffer_.append(s, s + count);
|
||||
return count;
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Char> struct test_stream : std::basic_ostream<Char> {
|
||||
private:
|
||||
// Hide all operator<< from std::basic_ostream<Char>.
|
||||
void_t<> operator<<(null<>);
|
||||
void_t<> operator<<(const Char*);
|
||||
|
||||
template <typename T, FMT_ENABLE_IF(std::is_convertible<T, int>::value &&
|
||||
!std::is_enum<T>::value)>
|
||||
void_t<> operator<<(T);
|
||||
};
|
||||
|
||||
// Checks if T has a user-defined operator<< (e.g. not a member of
|
||||
// std::ostream).
|
||||
template <typename T, typename Char> class is_streamable {
|
||||
private:
|
||||
template <typename U>
|
||||
static bool_constant<!std::is_same<decltype(std::declval<test_stream<Char>&>()
|
||||
<< std::declval<U>()),
|
||||
void_t<>>::value>
|
||||
test(int);
|
||||
|
||||
template <typename> static std::false_type test(...);
|
||||
|
||||
using result = decltype(test<T>(0));
|
||||
|
||||
public:
|
||||
static const bool value = result::value;
|
||||
};
|
||||
|
||||
// Write the content of buf to os.
|
||||
template <typename Char>
|
||||
void write_buffer(std::basic_ostream<Char>& os, buffer<Char>& buf) {
|
||||
const Char* buf_data = buf.data();
|
||||
using unsigned_streamsize = std::make_unsigned<std::streamsize>::type;
|
||||
unsigned_streamsize size = buf.size();
|
||||
unsigned_streamsize max_size = to_unsigned(max_value<std::streamsize>());
|
||||
do {
|
||||
unsigned_streamsize n = size <= max_size ? size : max_size;
|
||||
os.write(buf_data, static_cast<std::streamsize>(n));
|
||||
buf_data += n;
|
||||
size -= n;
|
||||
} while (size != 0);
|
||||
}
|
||||
|
||||
template <typename Char, typename T>
|
||||
void format_value(buffer<Char>& buf, const T& value,
|
||||
locale_ref loc = locale_ref()) {
|
||||
formatbuf<Char> format_buf(buf);
|
||||
std::basic_ostream<Char> output(&format_buf);
|
||||
#if !defined(FMT_STATIC_THOUSANDS_SEPARATOR)
|
||||
if (loc) output.imbue(loc.get<std::locale>());
|
||||
#endif
|
||||
output << value;
|
||||
output.exceptions(std::ios_base::failbit | std::ios_base::badbit);
|
||||
buf.resize(buf.size());
|
||||
}
|
||||
|
||||
// Formats an object of type T that has an overloaded ostream operator<<.
|
||||
template <typename T, typename Char>
|
||||
struct fallback_formatter<T, Char, enable_if_t<is_streamable<T, Char>::value>>
|
||||
: private formatter<basic_string_view<Char>, Char> {
|
||||
FMT_CONSTEXPR auto parse(basic_format_parse_context<Char>& ctx)
|
||||
-> decltype(ctx.begin()) {
|
||||
return formatter<basic_string_view<Char>, Char>::parse(ctx);
|
||||
}
|
||||
template <typename ParseCtx,
|
||||
FMT_ENABLE_IF(std::is_same<
|
||||
ParseCtx, basic_printf_parse_context<Char>>::value)>
|
||||
auto parse(ParseCtx& ctx) -> decltype(ctx.begin()) {
|
||||
return ctx.begin();
|
||||
}
|
||||
|
||||
template <typename OutputIt>
|
||||
auto format(const T& value, basic_format_context<OutputIt, Char>& ctx)
|
||||
-> OutputIt {
|
||||
basic_memory_buffer<Char> buffer;
|
||||
format_value(buffer, value, ctx.locale());
|
||||
basic_string_view<Char> str(buffer.data(), buffer.size());
|
||||
return formatter<basic_string_view<Char>, Char>::format(str, ctx);
|
||||
}
|
||||
template <typename OutputIt>
|
||||
auto format(const T& value, basic_printf_context<OutputIt, Char>& ctx)
|
||||
-> OutputIt {
|
||||
basic_memory_buffer<Char> buffer;
|
||||
format_value(buffer, value, ctx.locale());
|
||||
return std::copy(buffer.begin(), buffer.end(), ctx.out());
|
||||
}
|
||||
};
|
||||
} // namespace detail
|
||||
|
||||
template <typename Char>
|
||||
void vprint(std::basic_ostream<Char>& os, basic_string_view<Char> format_str,
|
||||
basic_format_args<buffer_context<type_identity_t<Char>>> args) {
|
||||
basic_memory_buffer<Char> buffer;
|
||||
detail::vformat_to(buffer, format_str, args);
|
||||
detail::write_buffer(os, buffer);
|
||||
}
|
||||
|
||||
/**
|
||||
\rst
|
||||
Prints formatted data to the stream *os*.
|
||||
|
||||
**Example**::
|
||||
|
||||
fmt::print(cerr, "Don't {}!", "panic");
|
||||
\endrst
|
||||
*/
|
||||
template <typename S, typename... Args,
|
||||
typename Char = enable_if_t<detail::is_string<S>::value, char_t<S>>>
|
||||
void print(std::basic_ostream<Char>& os, const S& format_str, Args&&... args) {
|
||||
vprint(os, to_string_view(format_str),
|
||||
detail::make_args_checked<Args...>(format_str, args...));
|
||||
}
|
||||
FMT_END_NAMESPACE
|
||||
|
||||
#endif // FMT_OSTREAM_H_
|
||||
2
external/fmtlib/include/fmt/posix.h
vendored
Normal file
2
external/fmtlib/include/fmt/posix.h
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
#include "os.h"
|
||||
#warning "fmt/posix.h is deprecated; use fmt/os.h instead"
|
||||
751
external/fmtlib/include/fmt/printf.h
vendored
Normal file
751
external/fmtlib/include/fmt/printf.h
vendored
Normal file
@@ -0,0 +1,751 @@
|
||||
// Formatting library for C++ - legacy printf implementation
|
||||
//
|
||||
// Copyright (c) 2012 - 2016, Victor Zverovich
|
||||
// All rights reserved.
|
||||
//
|
||||
// For the license information refer to format.h.
|
||||
|
||||
#ifndef FMT_PRINTF_H_
|
||||
#define FMT_PRINTF_H_
|
||||
|
||||
#include <algorithm> // std::max
|
||||
#include <limits> // std::numeric_limits
|
||||
|
||||
#include "ostream.h"
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
namespace detail {
|
||||
|
||||
// Checks if a value fits in int - used to avoid warnings about comparing
|
||||
// signed and unsigned integers.
|
||||
template <bool IsSigned> struct int_checker {
|
||||
template <typename T> static bool fits_in_int(T value) {
|
||||
unsigned max = max_value<int>();
|
||||
return value <= max;
|
||||
}
|
||||
static bool fits_in_int(bool) { return true; }
|
||||
};
|
||||
|
||||
template <> struct int_checker<true> {
|
||||
template <typename T> static bool fits_in_int(T value) {
|
||||
return value >= (std::numeric_limits<int>::min)() &&
|
||||
value <= max_value<int>();
|
||||
}
|
||||
static bool fits_in_int(int) { return true; }
|
||||
};
|
||||
|
||||
class printf_precision_handler {
|
||||
public:
|
||||
template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
|
||||
int operator()(T value) {
|
||||
if (!int_checker<std::numeric_limits<T>::is_signed>::fits_in_int(value))
|
||||
FMT_THROW(format_error("number is too big"));
|
||||
return (std::max)(static_cast<int>(value), 0);
|
||||
}
|
||||
|
||||
template <typename T, FMT_ENABLE_IF(!std::is_integral<T>::value)>
|
||||
int operator()(T) {
|
||||
FMT_THROW(format_error("precision is not integer"));
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
// An argument visitor that returns true iff arg is a zero integer.
|
||||
class is_zero_int {
|
||||
public:
|
||||
template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
|
||||
bool operator()(T value) {
|
||||
return value == 0;
|
||||
}
|
||||
|
||||
template <typename T, FMT_ENABLE_IF(!std::is_integral<T>::value)>
|
||||
bool operator()(T) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T> struct make_unsigned_or_bool : std::make_unsigned<T> {};
|
||||
|
||||
template <> struct make_unsigned_or_bool<bool> { using type = bool; };
|
||||
|
||||
template <typename T, typename Context> class arg_converter {
|
||||
private:
|
||||
using char_type = typename Context::char_type;
|
||||
|
||||
basic_format_arg<Context>& arg_;
|
||||
char_type type_;
|
||||
|
||||
public:
|
||||
arg_converter(basic_format_arg<Context>& arg, char_type type)
|
||||
: arg_(arg), type_(type) {}
|
||||
|
||||
void operator()(bool value) {
|
||||
if (type_ != 's') operator()<bool>(value);
|
||||
}
|
||||
|
||||
template <typename U, FMT_ENABLE_IF(std::is_integral<U>::value)>
|
||||
void operator()(U value) {
|
||||
bool is_signed = type_ == 'd' || type_ == 'i';
|
||||
using target_type = conditional_t<std::is_same<T, void>::value, U, T>;
|
||||
if (const_check(sizeof(target_type) <= sizeof(int))) {
|
||||
// Extra casts are used to silence warnings.
|
||||
if (is_signed) {
|
||||
arg_ = detail::make_arg<Context>(
|
||||
static_cast<int>(static_cast<target_type>(value)));
|
||||
} else {
|
||||
using unsigned_type = typename make_unsigned_or_bool<target_type>::type;
|
||||
arg_ = detail::make_arg<Context>(
|
||||
static_cast<unsigned>(static_cast<unsigned_type>(value)));
|
||||
}
|
||||
} else {
|
||||
if (is_signed) {
|
||||
// glibc's printf doesn't sign extend arguments of smaller types:
|
||||
// std::printf("%lld", -42); // prints "4294967254"
|
||||
// but we don't have to do the same because it's a UB.
|
||||
arg_ = detail::make_arg<Context>(static_cast<long long>(value));
|
||||
} else {
|
||||
arg_ = detail::make_arg<Context>(
|
||||
static_cast<typename make_unsigned_or_bool<U>::type>(value));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename U, FMT_ENABLE_IF(!std::is_integral<U>::value)>
|
||||
void operator()(U) {} // No conversion needed for non-integral types.
|
||||
};
|
||||
|
||||
// Converts an integer argument to T for printf, if T is an integral type.
|
||||
// If T is void, the argument is converted to corresponding signed or unsigned
|
||||
// type depending on the type specifier: 'd' and 'i' - signed, other -
|
||||
// unsigned).
|
||||
template <typename T, typename Context, typename Char>
|
||||
void convert_arg(basic_format_arg<Context>& arg, Char type) {
|
||||
visit_format_arg(arg_converter<T, Context>(arg, type), arg);
|
||||
}
|
||||
|
||||
// Converts an integer argument to char for printf.
|
||||
template <typename Context> class char_converter {
|
||||
private:
|
||||
basic_format_arg<Context>& arg_;
|
||||
|
||||
public:
|
||||
explicit char_converter(basic_format_arg<Context>& arg) : arg_(arg) {}
|
||||
|
||||
template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
|
||||
void operator()(T value) {
|
||||
arg_ = detail::make_arg<Context>(
|
||||
static_cast<typename Context::char_type>(value));
|
||||
}
|
||||
|
||||
template <typename T, FMT_ENABLE_IF(!std::is_integral<T>::value)>
|
||||
void operator()(T) {} // No conversion needed for non-integral types.
|
||||
};
|
||||
|
||||
// An argument visitor that return a pointer to a C string if argument is a
|
||||
// string or null otherwise.
|
||||
template <typename Char> struct get_cstring {
|
||||
template <typename T> const Char* operator()(T) { return nullptr; }
|
||||
const Char* operator()(const Char* s) { return s; }
|
||||
};
|
||||
|
||||
// Checks if an argument is a valid printf width specifier and sets
|
||||
// left alignment if it is negative.
|
||||
template <typename Char> class printf_width_handler {
|
||||
private:
|
||||
using format_specs = basic_format_specs<Char>;
|
||||
|
||||
format_specs& specs_;
|
||||
|
||||
public:
|
||||
explicit printf_width_handler(format_specs& specs) : specs_(specs) {}
|
||||
|
||||
template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
|
||||
unsigned operator()(T value) {
|
||||
auto width = static_cast<uint32_or_64_or_128_t<T>>(value);
|
||||
if (detail::is_negative(value)) {
|
||||
specs_.align = align::left;
|
||||
width = 0 - width;
|
||||
}
|
||||
unsigned int_max = max_value<int>();
|
||||
if (width > int_max) FMT_THROW(format_error("number is too big"));
|
||||
return static_cast<unsigned>(width);
|
||||
}
|
||||
|
||||
template <typename T, FMT_ENABLE_IF(!std::is_integral<T>::value)>
|
||||
unsigned operator()(T) {
|
||||
FMT_THROW(format_error("width is not integer"));
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Char, typename Context>
|
||||
void vprintf(buffer<Char>& buf, basic_string_view<Char> format,
|
||||
basic_format_args<Context> args) {
|
||||
Context(std::back_inserter(buf), format, args).format();
|
||||
}
|
||||
} // namespace detail
|
||||
|
||||
// For printing into memory_buffer.
|
||||
template <typename Char, typename Context>
|
||||
FMT_DEPRECATED void printf(detail::buffer<Char>& buf,
|
||||
basic_string_view<Char> format,
|
||||
basic_format_args<Context> args) {
|
||||
return detail::vprintf(buf, format, args);
|
||||
}
|
||||
using detail::vprintf;
|
||||
|
||||
template <typename Char>
|
||||
class basic_printf_parse_context : public basic_format_parse_context<Char> {
|
||||
using basic_format_parse_context<Char>::basic_format_parse_context;
|
||||
};
|
||||
template <typename OutputIt, typename Char> class basic_printf_context;
|
||||
|
||||
/**
|
||||
\rst
|
||||
The ``printf`` argument formatter.
|
||||
\endrst
|
||||
*/
|
||||
template <typename OutputIt, typename Char>
|
||||
class printf_arg_formatter : public detail::arg_formatter_base<OutputIt, Char> {
|
||||
public:
|
||||
using iterator = OutputIt;
|
||||
|
||||
private:
|
||||
using char_type = Char;
|
||||
using base = detail::arg_formatter_base<OutputIt, Char>;
|
||||
using context_type = basic_printf_context<OutputIt, Char>;
|
||||
|
||||
context_type& context_;
|
||||
|
||||
void write_null_pointer(char) {
|
||||
this->specs()->type = 0;
|
||||
this->write("(nil)");
|
||||
}
|
||||
|
||||
void write_null_pointer(wchar_t) {
|
||||
this->specs()->type = 0;
|
||||
this->write(L"(nil)");
|
||||
}
|
||||
|
||||
public:
|
||||
using format_specs = typename base::format_specs;
|
||||
|
||||
/**
|
||||
\rst
|
||||
Constructs an argument formatter object.
|
||||
*buffer* is a reference to the output buffer and *specs* contains format
|
||||
specifier information for standard argument types.
|
||||
\endrst
|
||||
*/
|
||||
printf_arg_formatter(iterator iter, format_specs& specs, context_type& ctx)
|
||||
: base(iter, &specs, detail::locale_ref()), context_(ctx) {}
|
||||
|
||||
template <typename T, FMT_ENABLE_IF(fmt::detail::is_integral<T>::value)>
|
||||
iterator operator()(T value) {
|
||||
// MSVC2013 fails to compile separate overloads for bool and char_type so
|
||||
// use std::is_same instead.
|
||||
if (std::is_same<T, bool>::value) {
|
||||
format_specs& fmt_specs = *this->specs();
|
||||
if (fmt_specs.type != 's') return base::operator()(value ? 1 : 0);
|
||||
fmt_specs.type = 0;
|
||||
this->write(value != 0);
|
||||
} else if (std::is_same<T, char_type>::value) {
|
||||
format_specs& fmt_specs = *this->specs();
|
||||
if (fmt_specs.type && fmt_specs.type != 'c')
|
||||
return (*this)(static_cast<int>(value));
|
||||
fmt_specs.sign = sign::none;
|
||||
fmt_specs.alt = false;
|
||||
fmt_specs.fill[0] = ' '; // Ignore '0' flag for char types.
|
||||
// align::numeric needs to be overwritten here since the '0' flag is
|
||||
// ignored for non-numeric types
|
||||
if (fmt_specs.align == align::none || fmt_specs.align == align::numeric)
|
||||
fmt_specs.align = align::right;
|
||||
return base::operator()(value);
|
||||
} else {
|
||||
return base::operator()(value);
|
||||
}
|
||||
return this->out();
|
||||
}
|
||||
|
||||
template <typename T, FMT_ENABLE_IF(std::is_floating_point<T>::value)>
|
||||
iterator operator()(T value) {
|
||||
return base::operator()(value);
|
||||
}
|
||||
|
||||
/** Formats a null-terminated C string. */
|
||||
iterator operator()(const char* value) {
|
||||
if (value)
|
||||
base::operator()(value);
|
||||
else if (this->specs()->type == 'p')
|
||||
write_null_pointer(char_type());
|
||||
else
|
||||
this->write("(null)");
|
||||
return this->out();
|
||||
}
|
||||
|
||||
/** Formats a null-terminated wide C string. */
|
||||
iterator operator()(const wchar_t* value) {
|
||||
if (value)
|
||||
base::operator()(value);
|
||||
else if (this->specs()->type == 'p')
|
||||
write_null_pointer(char_type());
|
||||
else
|
||||
this->write(L"(null)");
|
||||
return this->out();
|
||||
}
|
||||
|
||||
iterator operator()(basic_string_view<char_type> value) {
|
||||
return base::operator()(value);
|
||||
}
|
||||
|
||||
iterator operator()(monostate value) { return base::operator()(value); }
|
||||
|
||||
/** Formats a pointer. */
|
||||
iterator operator()(const void* value) {
|
||||
if (value) return base::operator()(value);
|
||||
this->specs()->type = 0;
|
||||
write_null_pointer(char_type());
|
||||
return this->out();
|
||||
}
|
||||
|
||||
/** Formats an argument of a custom (user-defined) type. */
|
||||
iterator operator()(typename basic_format_arg<context_type>::handle handle) {
|
||||
handle.format(context_.parse_context(), context_);
|
||||
return this->out();
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T> struct printf_formatter {
|
||||
printf_formatter() = delete;
|
||||
|
||||
template <typename ParseContext>
|
||||
auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
|
||||
return ctx.begin();
|
||||
}
|
||||
|
||||
template <typename FormatContext>
|
||||
auto format(const T& value, FormatContext& ctx) -> decltype(ctx.out()) {
|
||||
detail::format_value(detail::get_container(ctx.out()), value);
|
||||
return ctx.out();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
This template formats data and writes the output through an output iterator.
|
||||
*/
|
||||
template <typename OutputIt, typename Char> class basic_printf_context {
|
||||
public:
|
||||
/** The character type for the output. */
|
||||
using char_type = Char;
|
||||
using iterator = OutputIt;
|
||||
using format_arg = basic_format_arg<basic_printf_context>;
|
||||
using parse_context_type = basic_printf_parse_context<Char>;
|
||||
template <typename T> using formatter_type = printf_formatter<T>;
|
||||
|
||||
private:
|
||||
using format_specs = basic_format_specs<char_type>;
|
||||
|
||||
OutputIt out_;
|
||||
basic_format_args<basic_printf_context> args_;
|
||||
parse_context_type parse_ctx_;
|
||||
|
||||
static void parse_flags(format_specs& specs, const Char*& it,
|
||||
const Char* end);
|
||||
|
||||
// Returns the argument with specified index or, if arg_index is -1, the next
|
||||
// argument.
|
||||
format_arg get_arg(int arg_index = -1);
|
||||
|
||||
// Parses argument index, flags and width and returns the argument index.
|
||||
int parse_header(const Char*& it, const Char* end, format_specs& specs);
|
||||
|
||||
public:
|
||||
/**
|
||||
\rst
|
||||
Constructs a ``printf_context`` object. References to the arguments are
|
||||
stored in the context object so make sure they have appropriate lifetimes.
|
||||
\endrst
|
||||
*/
|
||||
basic_printf_context(OutputIt out, basic_string_view<char_type> format_str,
|
||||
basic_format_args<basic_printf_context> args)
|
||||
: out_(out), args_(args), parse_ctx_(format_str) {}
|
||||
|
||||
OutputIt out() { return out_; }
|
||||
void advance_to(OutputIt it) { out_ = it; }
|
||||
|
||||
detail::locale_ref locale() { return {}; }
|
||||
|
||||
format_arg arg(int id) const { return args_.get(id); }
|
||||
|
||||
parse_context_type& parse_context() { return parse_ctx_; }
|
||||
|
||||
FMT_CONSTEXPR void on_error(const char* message) {
|
||||
parse_ctx_.on_error(message);
|
||||
}
|
||||
|
||||
/** Formats stored arguments and writes the output to the range. */
|
||||
template <typename ArgFormatter = printf_arg_formatter<OutputIt, Char>>
|
||||
OutputIt format();
|
||||
};
|
||||
|
||||
template <typename OutputIt, typename Char>
|
||||
void basic_printf_context<OutputIt, Char>::parse_flags(format_specs& specs,
|
||||
const Char*& it,
|
||||
const Char* end) {
|
||||
for (; it != end; ++it) {
|
||||
switch (*it) {
|
||||
case '-':
|
||||
specs.align = align::left;
|
||||
break;
|
||||
case '+':
|
||||
specs.sign = sign::plus;
|
||||
break;
|
||||
case '0':
|
||||
specs.fill[0] = '0';
|
||||
break;
|
||||
case ' ':
|
||||
if (specs.sign != sign::plus) {
|
||||
specs.sign = sign::space;
|
||||
}
|
||||
break;
|
||||
case '#':
|
||||
specs.alt = true;
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename OutputIt, typename Char>
|
||||
typename basic_printf_context<OutputIt, Char>::format_arg
|
||||
basic_printf_context<OutputIt, Char>::get_arg(int arg_index) {
|
||||
if (arg_index < 0)
|
||||
arg_index = parse_ctx_.next_arg_id();
|
||||
else
|
||||
parse_ctx_.check_arg_id(--arg_index);
|
||||
return detail::get_arg(*this, arg_index);
|
||||
}
|
||||
|
||||
template <typename OutputIt, typename Char>
|
||||
int basic_printf_context<OutputIt, Char>::parse_header(const Char*& it,
|
||||
const Char* end,
|
||||
format_specs& specs) {
|
||||
int arg_index = -1;
|
||||
char_type c = *it;
|
||||
if (c >= '0' && c <= '9') {
|
||||
// Parse an argument index (if followed by '$') or a width possibly
|
||||
// preceded with '0' flag(s).
|
||||
detail::error_handler eh;
|
||||
int value = parse_nonnegative_int(it, end, eh);
|
||||
if (it != end && *it == '$') { // value is an argument index
|
||||
++it;
|
||||
arg_index = value;
|
||||
} else {
|
||||
if (c == '0') specs.fill[0] = '0';
|
||||
if (value != 0) {
|
||||
// Nonzero value means that we parsed width and don't need to
|
||||
// parse it or flags again, so return now.
|
||||
specs.width = value;
|
||||
return arg_index;
|
||||
}
|
||||
}
|
||||
}
|
||||
parse_flags(specs, it, end);
|
||||
// Parse width.
|
||||
if (it != end) {
|
||||
if (*it >= '0' && *it <= '9') {
|
||||
detail::error_handler eh;
|
||||
specs.width = parse_nonnegative_int(it, end, eh);
|
||||
} else if (*it == '*') {
|
||||
++it;
|
||||
specs.width = static_cast<int>(visit_format_arg(
|
||||
detail::printf_width_handler<char_type>(specs), get_arg()));
|
||||
}
|
||||
}
|
||||
return arg_index;
|
||||
}
|
||||
|
||||
template <typename OutputIt, typename Char>
|
||||
template <typename ArgFormatter>
|
||||
OutputIt basic_printf_context<OutputIt, Char>::format() {
|
||||
auto out = this->out();
|
||||
const Char* start = parse_ctx_.begin();
|
||||
const Char* end = parse_ctx_.end();
|
||||
auto it = start;
|
||||
while (it != end) {
|
||||
char_type c = *it++;
|
||||
if (c != '%') continue;
|
||||
if (it != end && *it == c) {
|
||||
out = std::copy(start, it, out);
|
||||
start = ++it;
|
||||
continue;
|
||||
}
|
||||
out = std::copy(start, it - 1, out);
|
||||
|
||||
format_specs specs;
|
||||
specs.align = align::right;
|
||||
|
||||
// Parse argument index, flags and width.
|
||||
int arg_index = parse_header(it, end, specs);
|
||||
if (arg_index == 0) on_error("argument not found");
|
||||
|
||||
// Parse precision.
|
||||
if (it != end && *it == '.') {
|
||||
++it;
|
||||
c = it != end ? *it : 0;
|
||||
if ('0' <= c && c <= '9') {
|
||||
detail::error_handler eh;
|
||||
specs.precision = parse_nonnegative_int(it, end, eh);
|
||||
} else if (c == '*') {
|
||||
++it;
|
||||
specs.precision = static_cast<int>(
|
||||
visit_format_arg(detail::printf_precision_handler(), get_arg()));
|
||||
} else {
|
||||
specs.precision = 0;
|
||||
}
|
||||
}
|
||||
|
||||
format_arg arg = get_arg(arg_index);
|
||||
// For d, i, o, u, x, and X conversion specifiers, if a precision is
|
||||
// specified, the '0' flag is ignored
|
||||
if (specs.precision >= 0 && arg.is_integral())
|
||||
specs.fill[0] =
|
||||
' '; // Ignore '0' flag for non-numeric types or if '-' present.
|
||||
if (specs.precision >= 0 && arg.type() == detail::type::cstring_type) {
|
||||
auto str = visit_format_arg(detail::get_cstring<Char>(), arg);
|
||||
auto str_end = str + specs.precision;
|
||||
auto nul = std::find(str, str_end, Char());
|
||||
arg = detail::make_arg<basic_printf_context>(basic_string_view<Char>(
|
||||
str,
|
||||
detail::to_unsigned(nul != str_end ? nul - str : specs.precision)));
|
||||
}
|
||||
if (specs.alt && visit_format_arg(detail::is_zero_int(), arg))
|
||||
specs.alt = false;
|
||||
if (specs.fill[0] == '0') {
|
||||
if (arg.is_arithmetic() && specs.align != align::left)
|
||||
specs.align = align::numeric;
|
||||
else
|
||||
specs.fill[0] = ' '; // Ignore '0' flag for non-numeric types or if '-'
|
||||
// flag is also present.
|
||||
}
|
||||
|
||||
// Parse length and convert the argument to the required type.
|
||||
c = it != end ? *it++ : 0;
|
||||
char_type t = it != end ? *it : 0;
|
||||
using detail::convert_arg;
|
||||
switch (c) {
|
||||
case 'h':
|
||||
if (t == 'h') {
|
||||
++it;
|
||||
t = it != end ? *it : 0;
|
||||
convert_arg<signed char>(arg, t);
|
||||
} else {
|
||||
convert_arg<short>(arg, t);
|
||||
}
|
||||
break;
|
||||
case 'l':
|
||||
if (t == 'l') {
|
||||
++it;
|
||||
t = it != end ? *it : 0;
|
||||
convert_arg<long long>(arg, t);
|
||||
} else {
|
||||
convert_arg<long>(arg, t);
|
||||
}
|
||||
break;
|
||||
case 'j':
|
||||
convert_arg<intmax_t>(arg, t);
|
||||
break;
|
||||
case 'z':
|
||||
convert_arg<size_t>(arg, t);
|
||||
break;
|
||||
case 't':
|
||||
convert_arg<std::ptrdiff_t>(arg, t);
|
||||
break;
|
||||
case 'L':
|
||||
// printf produces garbage when 'L' is omitted for long double, no
|
||||
// need to do the same.
|
||||
break;
|
||||
default:
|
||||
--it;
|
||||
convert_arg<void>(arg, c);
|
||||
}
|
||||
|
||||
// Parse type.
|
||||
if (it == end) FMT_THROW(format_error("invalid format string"));
|
||||
specs.type = static_cast<char>(*it++);
|
||||
if (arg.is_integral()) {
|
||||
// Normalize type.
|
||||
switch (specs.type) {
|
||||
case 'i':
|
||||
case 'u':
|
||||
specs.type = 'd';
|
||||
break;
|
||||
case 'c':
|
||||
visit_format_arg(detail::char_converter<basic_printf_context>(arg),
|
||||
arg);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
start = it;
|
||||
|
||||
// Format argument.
|
||||
out = visit_format_arg(ArgFormatter(out, specs, *this), arg);
|
||||
}
|
||||
return std::copy(start, it, out);
|
||||
}
|
||||
|
||||
template <typename Char>
|
||||
using basic_printf_context_t =
|
||||
basic_printf_context<std::back_insert_iterator<detail::buffer<Char>>, Char>;
|
||||
|
||||
using printf_context = basic_printf_context_t<char>;
|
||||
using wprintf_context = basic_printf_context_t<wchar_t>;
|
||||
|
||||
using printf_args = basic_format_args<printf_context>;
|
||||
using wprintf_args = basic_format_args<wprintf_context>;
|
||||
|
||||
/**
|
||||
\rst
|
||||
Constructs an `~fmt::format_arg_store` object that contains references to
|
||||
arguments and can be implicitly converted to `~fmt::printf_args`.
|
||||
\endrst
|
||||
*/
|
||||
template <typename... Args>
|
||||
inline format_arg_store<printf_context, Args...> make_printf_args(
|
||||
const Args&... args) {
|
||||
return {args...};
|
||||
}
|
||||
|
||||
/**
|
||||
\rst
|
||||
Constructs an `~fmt::format_arg_store` object that contains references to
|
||||
arguments and can be implicitly converted to `~fmt::wprintf_args`.
|
||||
\endrst
|
||||
*/
|
||||
template <typename... Args>
|
||||
inline format_arg_store<wprintf_context, Args...> make_wprintf_args(
|
||||
const Args&... args) {
|
||||
return {args...};
|
||||
}
|
||||
|
||||
template <typename S, typename Char = char_t<S>>
|
||||
inline std::basic_string<Char> vsprintf(
|
||||
const S& format,
|
||||
basic_format_args<basic_printf_context_t<type_identity_t<Char>>> args) {
|
||||
basic_memory_buffer<Char> buffer;
|
||||
vprintf(buffer, to_string_view(format), args);
|
||||
return to_string(buffer);
|
||||
}
|
||||
|
||||
/**
|
||||
\rst
|
||||
Formats arguments and returns the result as a string.
|
||||
|
||||
**Example**::
|
||||
|
||||
std::string message = fmt::sprintf("The answer is %d", 42);
|
||||
\endrst
|
||||
*/
|
||||
template <typename S, typename... Args,
|
||||
typename Char = enable_if_t<detail::is_string<S>::value, char_t<S>>>
|
||||
inline std::basic_string<Char> sprintf(const S& format, const Args&... args) {
|
||||
using context = basic_printf_context_t<Char>;
|
||||
return vsprintf(to_string_view(format), make_format_args<context>(args...));
|
||||
}
|
||||
|
||||
template <typename S, typename Char = char_t<S>>
|
||||
inline int vfprintf(
|
||||
std::FILE* f, const S& format,
|
||||
basic_format_args<basic_printf_context_t<type_identity_t<Char>>> args) {
|
||||
basic_memory_buffer<Char> buffer;
|
||||
vprintf(buffer, to_string_view(format), args);
|
||||
size_t size = buffer.size();
|
||||
return std::fwrite(buffer.data(), sizeof(Char), size, f) < size
|
||||
? -1
|
||||
: static_cast<int>(size);
|
||||
}
|
||||
|
||||
/**
|
||||
\rst
|
||||
Prints formatted data to the file *f*.
|
||||
|
||||
**Example**::
|
||||
|
||||
fmt::fprintf(stderr, "Don't %s!", "panic");
|
||||
\endrst
|
||||
*/
|
||||
template <typename S, typename... Args,
|
||||
typename Char = enable_if_t<detail::is_string<S>::value, char_t<S>>>
|
||||
inline int fprintf(std::FILE* f, const S& format, const Args&... args) {
|
||||
using context = basic_printf_context_t<Char>;
|
||||
return vfprintf(f, to_string_view(format),
|
||||
make_format_args<context>(args...));
|
||||
}
|
||||
|
||||
template <typename S, typename Char = char_t<S>>
|
||||
inline int vprintf(
|
||||
const S& format,
|
||||
basic_format_args<basic_printf_context_t<type_identity_t<Char>>> args) {
|
||||
return vfprintf(stdout, to_string_view(format), args);
|
||||
}
|
||||
|
||||
/**
|
||||
\rst
|
||||
Prints formatted data to ``stdout``.
|
||||
|
||||
**Example**::
|
||||
|
||||
fmt::printf("Elapsed time: %.2f seconds", 1.23);
|
||||
\endrst
|
||||
*/
|
||||
template <typename S, typename... Args,
|
||||
FMT_ENABLE_IF(detail::is_string<S>::value)>
|
||||
inline int printf(const S& format_str, const Args&... args) {
|
||||
using context = basic_printf_context_t<char_t<S>>;
|
||||
return vprintf(to_string_view(format_str),
|
||||
make_format_args<context>(args...));
|
||||
}
|
||||
|
||||
template <typename S, typename Char = char_t<S>>
|
||||
inline int vfprintf(
|
||||
std::basic_ostream<Char>& os, const S& format,
|
||||
basic_format_args<basic_printf_context_t<type_identity_t<Char>>> args) {
|
||||
basic_memory_buffer<Char> buffer;
|
||||
vprintf(buffer, to_string_view(format), args);
|
||||
detail::write_buffer(os, buffer);
|
||||
return static_cast<int>(buffer.size());
|
||||
}
|
||||
|
||||
/** Formats arguments and writes the output to the range. */
|
||||
template <typename ArgFormatter, typename Char,
|
||||
typename Context =
|
||||
basic_printf_context<typename ArgFormatter::iterator, Char>>
|
||||
typename ArgFormatter::iterator vprintf(
|
||||
detail::buffer<Char>& out, basic_string_view<Char> format_str,
|
||||
basic_format_args<type_identity_t<Context>> args) {
|
||||
typename ArgFormatter::iterator iter(out);
|
||||
Context(iter, format_str, args).template format<ArgFormatter>();
|
||||
return iter;
|
||||
}
|
||||
|
||||
/**
|
||||
\rst
|
||||
Prints formatted data to the stream *os*.
|
||||
|
||||
**Example**::
|
||||
|
||||
fmt::fprintf(cerr, "Don't %s!", "panic");
|
||||
\endrst
|
||||
*/
|
||||
template <typename S, typename... Args, typename Char = char_t<S>>
|
||||
inline int fprintf(std::basic_ostream<Char>& os, const S& format_str,
|
||||
const Args&... args) {
|
||||
using context = basic_printf_context_t<Char>;
|
||||
return vfprintf(os, to_string_view(format_str),
|
||||
make_format_args<context>(args...));
|
||||
}
|
||||
FMT_END_NAMESPACE
|
||||
|
||||
#endif // FMT_PRINTF_H_
|
||||
386
external/fmtlib/include/fmt/ranges.h
vendored
Normal file
386
external/fmtlib/include/fmt/ranges.h
vendored
Normal file
@@ -0,0 +1,386 @@
|
||||
// Formatting library for C++ - experimental range support
|
||||
//
|
||||
// Copyright (c) 2012 - present, Victor Zverovich
|
||||
// All rights reserved.
|
||||
//
|
||||
// For the license information refer to format.h.
|
||||
//
|
||||
// Copyright (c) 2018 - present, Remotion (Igor Schulz)
|
||||
// All Rights Reserved
|
||||
// {fmt} support for ranges, containers and types tuple interface.
|
||||
|
||||
#ifndef FMT_RANGES_H_
|
||||
#define FMT_RANGES_H_
|
||||
|
||||
#include <initializer_list>
|
||||
#include <type_traits>
|
||||
|
||||
#include "format.h"
|
||||
|
||||
// output only up to N items from the range.
|
||||
#ifndef FMT_RANGE_OUTPUT_LENGTH_LIMIT
|
||||
# define FMT_RANGE_OUTPUT_LENGTH_LIMIT 256
|
||||
#endif
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
|
||||
template <typename Char> struct formatting_base {
|
||||
template <typename ParseContext>
|
||||
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
|
||||
return ctx.begin();
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Char, typename Enable = void>
|
||||
struct formatting_range : formatting_base<Char> {
|
||||
static FMT_CONSTEXPR_DECL const size_t range_length_limit =
|
||||
FMT_RANGE_OUTPUT_LENGTH_LIMIT; // output only up to N items from the
|
||||
// range.
|
||||
Char prefix;
|
||||
Char delimiter;
|
||||
Char postfix;
|
||||
formatting_range() : prefix('{'), delimiter(','), postfix('}') {}
|
||||
static FMT_CONSTEXPR_DECL const bool add_delimiter_spaces = true;
|
||||
static FMT_CONSTEXPR_DECL const bool add_prepostfix_space = false;
|
||||
};
|
||||
|
||||
template <typename Char, typename Enable = void>
|
||||
struct formatting_tuple : formatting_base<Char> {
|
||||
Char prefix;
|
||||
Char delimiter;
|
||||
Char postfix;
|
||||
formatting_tuple() : prefix('('), delimiter(','), postfix(')') {}
|
||||
static FMT_CONSTEXPR_DECL const bool add_delimiter_spaces = true;
|
||||
static FMT_CONSTEXPR_DECL const bool add_prepostfix_space = false;
|
||||
};
|
||||
|
||||
namespace detail {
|
||||
|
||||
template <typename RangeT, typename OutputIterator>
|
||||
OutputIterator copy(const RangeT& range, OutputIterator out) {
|
||||
for (auto it = range.begin(), end = range.end(); it != end; ++it)
|
||||
*out++ = *it;
|
||||
return out;
|
||||
}
|
||||
|
||||
template <typename OutputIterator>
|
||||
OutputIterator copy(const char* str, OutputIterator out) {
|
||||
while (*str) *out++ = *str++;
|
||||
return out;
|
||||
}
|
||||
|
||||
template <typename OutputIterator>
|
||||
OutputIterator copy(char ch, OutputIterator out) {
|
||||
*out++ = ch;
|
||||
return out;
|
||||
}
|
||||
|
||||
/// Return true value if T has std::string interface, like std::string_view.
|
||||
template <typename T> class is_like_std_string {
|
||||
template <typename U>
|
||||
static auto check(U* p)
|
||||
-> decltype((void)p->find('a'), p->length(), (void)p->data(), int());
|
||||
template <typename> static void check(...);
|
||||
|
||||
public:
|
||||
static FMT_CONSTEXPR_DECL const bool value =
|
||||
is_string<T>::value || !std::is_void<decltype(check<T>(nullptr))>::value;
|
||||
};
|
||||
|
||||
template <typename Char>
|
||||
struct is_like_std_string<fmt::basic_string_view<Char>> : std::true_type {};
|
||||
|
||||
template <typename... Ts> struct conditional_helper {};
|
||||
|
||||
template <typename T, typename _ = void> struct is_range_ : std::false_type {};
|
||||
|
||||
#if !FMT_MSC_VER || FMT_MSC_VER > 1800
|
||||
template <typename T>
|
||||
struct is_range_<
|
||||
T, conditional_t<false,
|
||||
conditional_helper<decltype(std::declval<T>().begin()),
|
||||
decltype(std::declval<T>().end())>,
|
||||
void>> : std::true_type {};
|
||||
#endif
|
||||
|
||||
/// tuple_size and tuple_element check.
|
||||
template <typename T> class is_tuple_like_ {
|
||||
template <typename U>
|
||||
static auto check(U* p) -> decltype(std::tuple_size<U>::value, int());
|
||||
template <typename> static void check(...);
|
||||
|
||||
public:
|
||||
static FMT_CONSTEXPR_DECL const bool value =
|
||||
!std::is_void<decltype(check<T>(nullptr))>::value;
|
||||
};
|
||||
|
||||
// Check for integer_sequence
|
||||
#if defined(__cpp_lib_integer_sequence) || FMT_MSC_VER >= 1900
|
||||
template <typename T, T... N>
|
||||
using integer_sequence = std::integer_sequence<T, N...>;
|
||||
template <size_t... N> using index_sequence = std::index_sequence<N...>;
|
||||
template <size_t N> using make_index_sequence = std::make_index_sequence<N>;
|
||||
#else
|
||||
template <typename T, T... N> struct integer_sequence {
|
||||
using value_type = T;
|
||||
|
||||
static FMT_CONSTEXPR size_t size() { return sizeof...(N); }
|
||||
};
|
||||
|
||||
template <size_t... N> using index_sequence = integer_sequence<size_t, N...>;
|
||||
|
||||
template <typename T, size_t N, T... Ns>
|
||||
struct make_integer_sequence : make_integer_sequence<T, N - 1, N - 1, Ns...> {};
|
||||
template <typename T, T... Ns>
|
||||
struct make_integer_sequence<T, 0, Ns...> : integer_sequence<T, Ns...> {};
|
||||
|
||||
template <size_t N>
|
||||
using make_index_sequence = make_integer_sequence<size_t, N>;
|
||||
#endif
|
||||
|
||||
template <class Tuple, class F, size_t... Is>
|
||||
void for_each(index_sequence<Is...>, Tuple&& tup, F&& f) FMT_NOEXCEPT {
|
||||
using std::get;
|
||||
// using free function get<I>(T) now.
|
||||
const int _[] = {0, ((void)f(get<Is>(tup)), 0)...};
|
||||
(void)_; // blocks warnings
|
||||
}
|
||||
|
||||
template <class T>
|
||||
FMT_CONSTEXPR make_index_sequence<std::tuple_size<T>::value> get_indexes(
|
||||
T const&) {
|
||||
return {};
|
||||
}
|
||||
|
||||
template <class Tuple, class F> void for_each(Tuple&& tup, F&& f) {
|
||||
const auto indexes = get_indexes(tup);
|
||||
for_each(indexes, std::forward<Tuple>(tup), std::forward<F>(f));
|
||||
}
|
||||
|
||||
template <typename Arg, FMT_ENABLE_IF(!is_like_std_string<
|
||||
typename std::decay<Arg>::type>::value)>
|
||||
FMT_CONSTEXPR const char* format_str_quoted(bool add_space, const Arg&) {
|
||||
return add_space ? " {}" : "{}";
|
||||
}
|
||||
|
||||
template <typename Arg, FMT_ENABLE_IF(is_like_std_string<
|
||||
typename std::decay<Arg>::type>::value)>
|
||||
FMT_CONSTEXPR const char* format_str_quoted(bool add_space, const Arg&) {
|
||||
return add_space ? " \"{}\"" : "\"{}\"";
|
||||
}
|
||||
|
||||
FMT_CONSTEXPR const char* format_str_quoted(bool add_space, const char*) {
|
||||
return add_space ? " \"{}\"" : "\"{}\"";
|
||||
}
|
||||
FMT_CONSTEXPR const wchar_t* format_str_quoted(bool add_space, const wchar_t*) {
|
||||
return add_space ? L" \"{}\"" : L"\"{}\"";
|
||||
}
|
||||
|
||||
FMT_CONSTEXPR const char* format_str_quoted(bool add_space, const char) {
|
||||
return add_space ? " '{}'" : "'{}'";
|
||||
}
|
||||
FMT_CONSTEXPR const wchar_t* format_str_quoted(bool add_space, const wchar_t) {
|
||||
return add_space ? L" '{}'" : L"'{}'";
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
template <typename T> struct is_tuple_like {
|
||||
static FMT_CONSTEXPR_DECL const bool value =
|
||||
detail::is_tuple_like_<T>::value && !detail::is_range_<T>::value;
|
||||
};
|
||||
|
||||
template <typename TupleT, typename Char>
|
||||
struct formatter<TupleT, Char, enable_if_t<fmt::is_tuple_like<TupleT>::value>> {
|
||||
private:
|
||||
// C++11 generic lambda for format()
|
||||
template <typename FormatContext> struct format_each {
|
||||
template <typename T> void operator()(const T& v) {
|
||||
if (i > 0) {
|
||||
if (formatting.add_prepostfix_space) {
|
||||
*out++ = ' ';
|
||||
}
|
||||
out = detail::copy(formatting.delimiter, out);
|
||||
}
|
||||
out = format_to(out,
|
||||
detail::format_str_quoted(
|
||||
(formatting.add_delimiter_spaces && i > 0), v),
|
||||
v);
|
||||
++i;
|
||||
}
|
||||
|
||||
formatting_tuple<Char>& formatting;
|
||||
size_t& i;
|
||||
typename std::add_lvalue_reference<decltype(
|
||||
std::declval<FormatContext>().out())>::type out;
|
||||
};
|
||||
|
||||
public:
|
||||
formatting_tuple<Char> formatting;
|
||||
|
||||
template <typename ParseContext>
|
||||
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
|
||||
return formatting.parse(ctx);
|
||||
}
|
||||
|
||||
template <typename FormatContext = format_context>
|
||||
auto format(const TupleT& values, FormatContext& ctx) -> decltype(ctx.out()) {
|
||||
auto out = ctx.out();
|
||||
size_t i = 0;
|
||||
detail::copy(formatting.prefix, out);
|
||||
|
||||
detail::for_each(values, format_each<FormatContext>{formatting, i, out});
|
||||
if (formatting.add_prepostfix_space) {
|
||||
*out++ = ' ';
|
||||
}
|
||||
detail::copy(formatting.postfix, out);
|
||||
|
||||
return ctx.out();
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T, typename Char> struct is_range {
|
||||
static FMT_CONSTEXPR_DECL const bool value =
|
||||
detail::is_range_<T>::value && !detail::is_like_std_string<T>::value &&
|
||||
!std::is_convertible<T, std::basic_string<Char>>::value &&
|
||||
!std::is_constructible<detail::std_string_view<Char>, T>::value;
|
||||
};
|
||||
|
||||
template <typename RangeT, typename Char>
|
||||
struct formatter<RangeT, Char,
|
||||
enable_if_t<fmt::is_range<RangeT, Char>::value>> {
|
||||
formatting_range<Char> formatting;
|
||||
|
||||
template <typename ParseContext>
|
||||
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
|
||||
return formatting.parse(ctx);
|
||||
}
|
||||
|
||||
template <typename FormatContext>
|
||||
typename FormatContext::iterator format(const RangeT& values,
|
||||
FormatContext& ctx) {
|
||||
auto out = detail::copy(formatting.prefix, ctx.out());
|
||||
size_t i = 0;
|
||||
auto it = values.begin();
|
||||
auto end = values.end();
|
||||
for (; it != end; ++it) {
|
||||
if (i > 0) {
|
||||
if (formatting.add_prepostfix_space) *out++ = ' ';
|
||||
out = detail::copy(formatting.delimiter, out);
|
||||
}
|
||||
out = format_to(out,
|
||||
detail::format_str_quoted(
|
||||
(formatting.add_delimiter_spaces && i > 0), *it),
|
||||
*it);
|
||||
if (++i > formatting.range_length_limit) {
|
||||
out = format_to(out, " ... <other elements>");
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (formatting.add_prepostfix_space) *out++ = ' ';
|
||||
return detail::copy(formatting.postfix, out);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Char, typename... T> struct tuple_arg_join : detail::view {
|
||||
const std::tuple<T...>& tuple;
|
||||
basic_string_view<Char> sep;
|
||||
|
||||
tuple_arg_join(const std::tuple<T...>& t, basic_string_view<Char> s)
|
||||
: tuple{t}, sep{s} {}
|
||||
};
|
||||
|
||||
template <typename Char, typename... T>
|
||||
struct formatter<tuple_arg_join<Char, T...>, Char> {
|
||||
template <typename ParseContext>
|
||||
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
|
||||
return ctx.begin();
|
||||
}
|
||||
|
||||
template <typename FormatContext>
|
||||
typename FormatContext::iterator format(
|
||||
const tuple_arg_join<Char, T...>& value, FormatContext& ctx) {
|
||||
return format(value, ctx, detail::make_index_sequence<sizeof...(T)>{});
|
||||
}
|
||||
|
||||
private:
|
||||
template <typename FormatContext, size_t... N>
|
||||
typename FormatContext::iterator format(
|
||||
const tuple_arg_join<Char, T...>& value, FormatContext& ctx,
|
||||
detail::index_sequence<N...>) {
|
||||
return format_args(value, ctx, std::get<N>(value.tuple)...);
|
||||
}
|
||||
|
||||
template <typename FormatContext>
|
||||
typename FormatContext::iterator format_args(
|
||||
const tuple_arg_join<Char, T...>&, FormatContext& ctx) {
|
||||
// NOTE: for compilers that support C++17, this empty function instantiation
|
||||
// can be replaced with a constexpr branch in the variadic overload.
|
||||
return ctx.out();
|
||||
}
|
||||
|
||||
template <typename FormatContext, typename Arg, typename... Args>
|
||||
typename FormatContext::iterator format_args(
|
||||
const tuple_arg_join<Char, T...>& value, FormatContext& ctx,
|
||||
const Arg& arg, const Args&... args) {
|
||||
using base = formatter<typename std::decay<Arg>::type, Char>;
|
||||
auto out = ctx.out();
|
||||
out = base{}.format(arg, ctx);
|
||||
if (sizeof...(Args) > 0) {
|
||||
out = std::copy(value.sep.begin(), value.sep.end(), out);
|
||||
ctx.advance_to(out);
|
||||
return format_args(value, ctx, args...);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
\rst
|
||||
Returns an object that formats `tuple` with elements separated by `sep`.
|
||||
|
||||
**Example**::
|
||||
|
||||
std::tuple<int, char> t = {1, 'a'};
|
||||
fmt::print("{}", fmt::join(t, ", "));
|
||||
// Output: "1, a"
|
||||
\endrst
|
||||
*/
|
||||
template <typename... T>
|
||||
FMT_CONSTEXPR tuple_arg_join<char, T...> join(const std::tuple<T...>& tuple,
|
||||
string_view sep) {
|
||||
return {tuple, sep};
|
||||
}
|
||||
|
||||
template <typename... T>
|
||||
FMT_CONSTEXPR tuple_arg_join<wchar_t, T...> join(const std::tuple<T...>& tuple,
|
||||
wstring_view sep) {
|
||||
return {tuple, sep};
|
||||
}
|
||||
|
||||
/**
|
||||
\rst
|
||||
Returns an object that formats `initializer_list` with elements separated by
|
||||
`sep`.
|
||||
|
||||
**Example**::
|
||||
|
||||
fmt::print("{}", fmt::join({1, 2, 3}, ", "));
|
||||
// Output: "1, 2, 3"
|
||||
\endrst
|
||||
*/
|
||||
template <typename T>
|
||||
arg_join<const T*, const T*, char> join(std::initializer_list<T> list,
|
||||
string_view sep) {
|
||||
return join(std::begin(list), std::end(list), sep);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
arg_join<const T*, const T*, wchar_t> join(std::initializer_list<T> list,
|
||||
wstring_view sep) {
|
||||
return join(std::begin(list), std::end(list), sep);
|
||||
}
|
||||
|
||||
FMT_END_NAMESPACE
|
||||
|
||||
#endif // FMT_RANGES_H_
|
||||
@@ -1,532 +0,0 @@
|
||||
/*
|
||||
Copyright 2016 SINTEF ICT, Applied Mathematics.
|
||||
Copyright 2016 Statoil ASA.
|
||||
Copyright 2022 Equinor ASA
|
||||
|
||||
This file is part of the Open Porous Media Project (OPM).
|
||||
|
||||
OPM is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
OPM is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with OPM. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef OPM_UTILITY_CSRGRAPHFROMCOORDINATES_HPP
|
||||
#define OPM_UTILITY_CSRGRAPHFROMCOORDINATES_HPP
|
||||
|
||||
#include <cstddef>
|
||||
#include <optional>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
/// \file
|
||||
///
|
||||
/// Facility for converting collection of region ID pairs into a sparse
|
||||
/// (CSR) adjacency matrix representation of a graph. Supports O(nnz)
|
||||
/// compression.
|
||||
|
||||
namespace Opm { namespace utility {
|
||||
|
||||
/// Form CSR adjacency matrix representation of unstructured graphs.
|
||||
/// Optionally maps vertex pairs to compressed indices to enable O(1)
|
||||
/// per-element lookup in assembly-like operations.
|
||||
///
|
||||
/// \tparam VertexID ID type of an abstract vertex in the graph. Could
|
||||
/// for instance be used to represent a cell index or a region index.
|
||||
/// Must be an integral type.
|
||||
///
|
||||
/// \tparam TrackCompressedIdx Whether or not to form a mapping relation
|
||||
/// for vertex pairs to compressed indices. Default value, false,
|
||||
/// bypasses this mapping relation and conserves memory.
|
||||
///
|
||||
/// \tparam PermitSelfConnections Whether or not to allow connections of
|
||||
/// the form i->i--i.e., diagonal elements. Default value, \c false,
|
||||
/// does not generate connections from a vertex to itself.
|
||||
template <typename VertexID = int, bool TrackCompressedIdx = false, bool PermitSelfConnections = false>
|
||||
class CSRGraphFromCoordinates
|
||||
{
|
||||
private:
|
||||
using BaseVertexID = std::remove_cv_t<VertexID>;
|
||||
|
||||
static_assert(std::is_integral_v<BaseVertexID>,
|
||||
"The VertexID must be an integral type");
|
||||
|
||||
public:
|
||||
/// Representation of neighbouring regions.
|
||||
using Neighbours = std::vector<BaseVertexID>;
|
||||
|
||||
/// Offset into neighbour array.
|
||||
using Offset = typename Neighbours::size_type;
|
||||
|
||||
/// CSR start pointers.
|
||||
using Start = std::vector<Offset>;
|
||||
|
||||
/// Clear all internal buffers, but preserve allocated capacity.
|
||||
void clear();
|
||||
|
||||
/// Add flow rate connection between regions.
|
||||
///
|
||||
/// \param[in] v1 First vertex in vertex pair. Used as row index.
|
||||
///
|
||||
/// \param[in] v2 Second vertex in vertex pair. Used as column index.
|
||||
///
|
||||
/// If both vertex IDs are the same, and class template argument \c
|
||||
/// PermitSelfConnections is in its default state of \c false, then
|
||||
/// this function does nothing.
|
||||
void addConnection(VertexID v1, VertexID v2);
|
||||
|
||||
/// Form CSR adjacency matrix representation of input graph from
|
||||
/// connections established in previous calls to addConnection().
|
||||
///
|
||||
/// \param[in] maxNumVertices Number of rows in resulting CSR
|
||||
/// matrix. If prior calls to addConnection() supply vertex IDs
|
||||
/// (row indices) greater than or equal to \p maxNumVertices,
|
||||
/// then method compress() will throw \code
|
||||
/// std::invalid_argument \endcode.
|
||||
///
|
||||
/// \param[in] expandExistingIdxMap Whether or not preserve and
|
||||
/// update the existing compressed index map. This is
|
||||
/// potentially useful for the case of adding new connections to
|
||||
/// an already compressed graph. The default setting, \c false,
|
||||
/// will disregard any existing index map and create the index
|
||||
/// map from scratch. This runtime parameter is unused if the
|
||||
/// class is not configured to track compressed indices through
|
||||
/// the TrackCompressedIdx class parameter.
|
||||
void compress(Offset maxNumVertices,
|
||||
bool expandExistingIdxMap = false);
|
||||
|
||||
/// Retrieve number of rows (source entities) in input graph.
|
||||
/// Corresponds to value of argument passed to compress(). Valid
|
||||
/// only after calling compress().
|
||||
Offset numVertices() const;
|
||||
|
||||
/// Retrieve number of edges (non-zero matrix elements) in input
|
||||
/// graph.
|
||||
Offset numEdges() const;
|
||||
|
||||
/// Read-only access to compressed structure's start pointers.
|
||||
const Start& startPointers() const
|
||||
{
|
||||
return this->csr_.startPointers();
|
||||
}
|
||||
|
||||
/// Read-only access to compressed structure's column indices,
|
||||
/// ascendingly sorted per row.
|
||||
const Neighbours& columnIndices() const
|
||||
{
|
||||
return this->csr_.columnIndices();
|
||||
}
|
||||
|
||||
/// Read-only access to mapping from input order vertex pairs to
|
||||
/// compressed structure's edge index (location in ja/sa).
|
||||
///
|
||||
/// Available only if client code sets TrackCompressedIdx=true.
|
||||
/// Compiler diagnostic, typically referring to 'enable_if', if
|
||||
/// client code tries to call this function without setting
|
||||
/// class parameter TrackCompressedIdx=true.
|
||||
template <typename Ret = const Start&>
|
||||
std::enable_if_t<TrackCompressedIdx, Ret> compressedIndexMap() const
|
||||
{
|
||||
return this->csr_.compressedIndexMap();
|
||||
}
|
||||
|
||||
// MessageBufferType API should be similar to Dune::MessageBufferIF
|
||||
template <class MessageBufferType>
|
||||
void write(MessageBufferType& buffer) const
|
||||
{
|
||||
this->csr_.write(buffer);
|
||||
}
|
||||
|
||||
// MessageBufferType API should be similar to Dune::MessageBufferIF
|
||||
template <class MessageBufferType>
|
||||
void read(MessageBufferType& buffer)
|
||||
{
|
||||
auto other = CSR{};
|
||||
other.read(buffer);
|
||||
|
||||
this->uncompressed_
|
||||
.add(other.maxRowID(),
|
||||
other.maxColID(),
|
||||
other.coordinateFormatRowIndices(),
|
||||
other.columnIndices());
|
||||
}
|
||||
|
||||
private:
|
||||
/// Coordinate format representation of individual contributions to
|
||||
/// inter-region flows.
|
||||
class Connections
|
||||
{
|
||||
public:
|
||||
/// Add contributions from a single inter-region connection.
|
||||
///
|
||||
/// \param[in] r1 Source region. Zero-based region index/ID.
|
||||
///
|
||||
/// \param[in] r2 Destination region. Zero-based region index.
|
||||
void add(VertexID v1, VertexID v2);
|
||||
|
||||
/// Add contributions from multiple inter-region connections.
|
||||
///
|
||||
/// \param[in] maxRowIdx Maximum row (source region) index
|
||||
/// across all new inter-region connection contributions.
|
||||
///
|
||||
/// \param[in] maxColIdx Maximum column (destination region)
|
||||
/// index across all new inter-region contributions.
|
||||
///
|
||||
/// \param[in] rows Source region indices for all new
|
||||
/// inter-region connection contributions.
|
||||
///
|
||||
/// \param[in] cols Destination region indices for all new
|
||||
/// inter-region connection contributions.
|
||||
void add(VertexID maxRowIdx,
|
||||
VertexID maxColIdx,
|
||||
const Neighbours& rows,
|
||||
const Neighbours& cols);
|
||||
|
||||
/// Clear internal tables. Preserve allocated capacity.
|
||||
void clear();
|
||||
|
||||
/// Predicate.
|
||||
///
|
||||
/// \return Whether or not internal tables are empty.
|
||||
bool empty() const;
|
||||
|
||||
/// Whether or not internal tables meet size consistency
|
||||
/// requirements.
|
||||
bool isValid() const;
|
||||
|
||||
/// Maximum zero-based row index.
|
||||
std::optional<BaseVertexID> maxRow() const;
|
||||
|
||||
/// Maximum zero-based column index.
|
||||
std::optional<BaseVertexID> maxCol() const;
|
||||
|
||||
/// Number of uncompressed contributions in internal tables.
|
||||
typename Neighbours::size_type numContributions() const;
|
||||
|
||||
/// Read-only access to uncompressed row indices.
|
||||
const Neighbours& rowIndices() const;
|
||||
|
||||
/// Read-only access to uncompressed column indices.
|
||||
const Neighbours& columnIndices() const;
|
||||
|
||||
private:
|
||||
/// Zero-based row/source region indices.
|
||||
Neighbours i_{};
|
||||
|
||||
/// Zero-based column/destination region indices.
|
||||
Neighbours j_{};
|
||||
|
||||
/// Maximum row index in \code this->i_ \endcode.
|
||||
std::optional<VertexID> max_i_{};
|
||||
|
||||
/// Maximum column index in \code this->j_ \endcode.
|
||||
std::optional<VertexID> max_j_{};
|
||||
};
|
||||
|
||||
/// Compressed sparse row representation of inter-region flow rates
|
||||
///
|
||||
/// Row and column indices are zero-based vertex IDs. Column
|
||||
/// indices ascendingly sorted per row.
|
||||
class CSR
|
||||
{
|
||||
public:
|
||||
/// Merge coordinate format into existing CSR map.
|
||||
///
|
||||
/// \param[in] conns Coordinate representation of new
|
||||
/// contributions.
|
||||
///
|
||||
/// \param[in] maxNumVertices Maximum number of vertices.
|
||||
///
|
||||
/// \param[in] expandExistingIdxMap Whether or not preserve and
|
||||
/// update the existing compressed index map. This is
|
||||
/// potentially useful for the case of adding new connections
|
||||
/// to an already compressed graph. The default setting, \c
|
||||
/// false, will disregard any existing index map and create
|
||||
/// the index map from scratch. This runtime parameter is
|
||||
/// unused if the class is not configured to track compressed
|
||||
/// indices through the TrackCompressedIdx class parameter.
|
||||
void merge(const Connections& conns,
|
||||
const Offset maxNumVertices,
|
||||
const bool expandExistingIdxMap);
|
||||
|
||||
/// Total number of rows in compressed map structure.
|
||||
Offset numRows() const;
|
||||
|
||||
/// Maximum zero-based row index encountered in mapped structure.
|
||||
BaseVertexID maxRowID() const;
|
||||
|
||||
/// Maximum zero-based column index encountered in mapped structure.
|
||||
BaseVertexID maxColID() const;
|
||||
|
||||
/// Read-only access to compressed structure's start pointers.
|
||||
const Start& startPointers() const;
|
||||
|
||||
/// Read-only access to compressed structure's column indices,
|
||||
/// ascendingly sorted per rwo.
|
||||
const Neighbours& columnIndices() const;
|
||||
|
||||
/// Coordinate format row index vector. Expanded from \code
|
||||
/// startPointers() \endcode.
|
||||
Neighbours coordinateFormatRowIndices() const;
|
||||
|
||||
template <typename Ret = const Start&>
|
||||
std::enable_if_t<TrackCompressedIdx, Ret> compressedIndexMap() const
|
||||
{
|
||||
return this->compressedIdx_;
|
||||
}
|
||||
|
||||
// MessageBufferType API should be similar to Dune::MessageBufferIF
|
||||
template <class MessageBufferType>
|
||||
void write(MessageBufferType& buffer) const
|
||||
{
|
||||
this->writeVector(this->ia_, buffer);
|
||||
this->writeVector(this->ja_, buffer);
|
||||
|
||||
if constexpr (TrackCompressedIdx) {
|
||||
this->writeVector(this->compressedIdx_, buffer);
|
||||
}
|
||||
|
||||
buffer.write(this->numRows_);
|
||||
buffer.write(this->numCols_);
|
||||
}
|
||||
|
||||
// MessageBufferType API should be similar to Dune::MessageBufferIF
|
||||
template <class MessageBufferType>
|
||||
void read(MessageBufferType& buffer)
|
||||
{
|
||||
this->readVector(buffer, this->ia_);
|
||||
this->readVector(buffer, this->ja_);
|
||||
|
||||
if constexpr (TrackCompressedIdx) {
|
||||
this->readVector(buffer, this->compressedIdx_);
|
||||
}
|
||||
|
||||
buffer.read(this->numRows_);
|
||||
buffer.read(this->numCols_);
|
||||
}
|
||||
|
||||
/// Clear internal tables. Preserve allocated capacity.
|
||||
void clear();
|
||||
|
||||
private:
|
||||
struct EmptyPlaceHolder {};
|
||||
|
||||
/// Start pointers.
|
||||
Start ia_{};
|
||||
|
||||
/// Column indices. Ascendingly sorted per row once structure
|
||||
/// is fully established.
|
||||
Neighbours ja_{};
|
||||
|
||||
/// Destination index in compressed representation. Vector of
|
||||
/// size equal to number of \c addConnection() calls if client
|
||||
/// code requests that compressed indices be tracked (i.e., when
|
||||
/// parameter TrackCompressedIdx == true); Empty structure
|
||||
/// otherwise (default setting).
|
||||
std::conditional_t<TrackCompressedIdx, Start, EmptyPlaceHolder> compressedIdx_{};
|
||||
|
||||
/// Number of active rows in compressed map structure.
|
||||
BaseVertexID numRows_{};
|
||||
|
||||
/// Number of active columns in compressed map structure.
|
||||
/// Tracked as the maximum column index plus one.
|
||||
BaseVertexID numCols_{};
|
||||
|
||||
// ---------------------------------------------------------
|
||||
// Implementation of read()/write()
|
||||
// ---------------------------------------------------------
|
||||
|
||||
template <typename T, class A, class MessageBufferType>
|
||||
void writeVector(const std::vector<T,A>& vec,
|
||||
MessageBufferType& buffer) const
|
||||
{
|
||||
const auto n = vec.size();
|
||||
buffer.write(n);
|
||||
|
||||
for (const auto& x : vec) {
|
||||
buffer.write(x);
|
||||
}
|
||||
}
|
||||
|
||||
template <class MessageBufferType, typename T, class A>
|
||||
void readVector(MessageBufferType& buffer,
|
||||
std::vector<T,A>& vec)
|
||||
{
|
||||
auto n = 0 * vec.size();
|
||||
buffer.read(n);
|
||||
|
||||
vec.resize(n);
|
||||
|
||||
for (auto& x : vec) {
|
||||
buffer.read(x);
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------
|
||||
// Implementation of merge()
|
||||
// ---------------------------------------------------------
|
||||
|
||||
/// Incorporate new, coordinate format contributions into
|
||||
/// existing, possibly empty, CSR mapping structure.
|
||||
///
|
||||
/// On exit the ia_ array holds the proper start pointers while
|
||||
/// ja_ holds the corresponding column indices albeit possibly
|
||||
/// repeated and unsorted.
|
||||
///
|
||||
/// \param[in] rows Row indices of all, possibly repeated,
|
||||
/// coordinate format input contributions. Start pointers \c
|
||||
/// ia_ updated to account for new entries.
|
||||
///
|
||||
/// \param[in] cols Column index of coordinate format intput
|
||||
/// structure. Inserted into \c ja_ according to its
|
||||
/// corresponding row index.
|
||||
///
|
||||
/// \param[in] maxRowID Maximum index in \p rows. Needed to
|
||||
/// ensure proper size of \c ia_.
|
||||
///
|
||||
/// \param[in] maxColID Maximum index in \p cols.
|
||||
///
|
||||
/// \param[in] expandExistingIdxMap Whether or not preserve and
|
||||
/// update the existing compressed index map. This is
|
||||
/// potentially useful for the case of adding new connections
|
||||
/// to an already compressed graph. The default setting, \c
|
||||
/// false, will disregard any existing index map and create
|
||||
/// the index map from scratch. This runtime parameter is
|
||||
/// unused if the class is not configured to track compressed
|
||||
/// indices through the TrackCompressedIdx class parameter.
|
||||
void assemble(const Neighbours& rows,
|
||||
const Neighbours& cols,
|
||||
BaseVertexID maxRowID,
|
||||
BaseVertexID maxColID,
|
||||
bool expandExistingIdxMap);
|
||||
|
||||
/// Sort column indices per row and compress repeated column
|
||||
/// indices down to a single unique element per row. Sum
|
||||
/// repeated values
|
||||
///
|
||||
/// On exit the \c ia_ and \c ja_ arrays all have their
|
||||
/// expected, canonical structure.
|
||||
///
|
||||
/// \param[in] maxNumVertices Maximum number of vertices
|
||||
/// supported by final compressed mapping structure. Ignored
|
||||
/// if less than active number of rows.
|
||||
void compress(const Offset maxNumVertices);
|
||||
|
||||
/// Sort column indices within each mapped row.
|
||||
///
|
||||
/// On exit \c ja_ has ascendingly sorted column indices, albeit
|
||||
/// possibly with repeated entries. This function also updates
|
||||
/// \c compressedIdx_, if applicable, to account for the new
|
||||
/// locations of the non-zero elements in the grouped structure.
|
||||
void sortColumnIndicesPerRow();
|
||||
|
||||
/// Condense repeated column indices per row down to a single
|
||||
/// unique entry for each.
|
||||
///
|
||||
/// Assumes that each row has ascendingly sorted column indices
|
||||
/// in \c ja_ and must therefore be called after member function
|
||||
/// sortColumnIndicesPerRow(). On exit, \c ja_ has its final
|
||||
/// canonical structure and \c compressedIdx_, if applicable,
|
||||
/// knows the final location of each non-zero contribution in
|
||||
/// the input coordinate format.
|
||||
void condenseDuplicates();
|
||||
|
||||
// ---------------------------------------------------------
|
||||
// Implementation of assemble()
|
||||
// ---------------------------------------------------------
|
||||
|
||||
/// Position end pointers at start of row to prepare for column
|
||||
/// index grouping by corresponding row index.
|
||||
///
|
||||
/// Also counts total number of non-zero elements, possibly
|
||||
/// including repetitions, in \code this->ia_[0] \endcode.
|
||||
///
|
||||
/// \param[in] numRows Number of rows in final compressed
|
||||
/// structure. Used to allocate \code this->ia_ \endcode.
|
||||
///
|
||||
/// \param[in] rowIdx Row indices of all, possibly repeated,
|
||||
/// coordinate format input contributions. Needed to count
|
||||
/// the number of possibly repeated column index entries per
|
||||
/// row.
|
||||
void preparePushbackRowGrouping(const int numRows,
|
||||
const Neighbours& rowIdx);
|
||||
|
||||
/// Group column indices by corresponding row index and track
|
||||
/// grouped location of original coordinate format element
|
||||
///
|
||||
/// Appends grouped location to \c compressedIdx_ if needed.
|
||||
///
|
||||
/// \param[in] rowIdx Row index of coordinate format input
|
||||
/// structure. Used as grouping key.
|
||||
///
|
||||
/// \param[in] colIdx Column index of coordinate format intput
|
||||
/// structure. Inserted into \c ja_ according to its
|
||||
/// corresponding row index.
|
||||
void groupAndTrackColumnIndicesByRow(const Neighbours& rowIdx,
|
||||
const Neighbours& colIdx);
|
||||
|
||||
// ---------------------------------------------------------
|
||||
// General utilities
|
||||
// ---------------------------------------------------------
|
||||
|
||||
/// Transpose connectivity structure.
|
||||
///
|
||||
/// Essentially swaps the roles of rows and columns. Also used
|
||||
/// as a basic building block for sortColumnIndicesPerRow().
|
||||
void transpose();
|
||||
|
||||
/// Condense sequences of repeated column indices in a single
|
||||
/// map row down to a single copy of each unique column index.
|
||||
///
|
||||
/// Appends new unique column indices to \code ja_ \endcode
|
||||
///
|
||||
/// Assumes that the map row has ascendingly sorted column
|
||||
/// indices and therefore has the same requirements as
|
||||
/// std::unique. Will also update the internal compressedIdx_
|
||||
/// mapping, if needed, to record new compressed locations for
|
||||
/// the current, uncompressed, non-zero map elements.
|
||||
///
|
||||
/// \param[in] begin Start of map row that contains possibly
|
||||
/// repeated column indices.
|
||||
///
|
||||
/// \param[in] end One-past-end of map row that contains
|
||||
/// possibly repeated column indices.
|
||||
void condenseAndTrackUniqueColumnsForSingleRow(typename Neighbours::const_iterator begin,
|
||||
typename Neighbours::const_iterator end);
|
||||
|
||||
/// Update \c compressedIdx_ mapping, if needed, to account for
|
||||
/// column index reshuffling.
|
||||
///
|
||||
/// \param[in] compressedIdx New compressed index locations of
|
||||
/// the non-zero map entries.
|
||||
///
|
||||
/// \param[in] numOrigNNZ Number of existing, unique NNZs
|
||||
/// (edges) in graph. Needed to support calling add() after
|
||||
/// compress() when TrackCompressedIdx is true.
|
||||
void remapCompressedIndex(Start&& compressedIdx,
|
||||
std::optional<typename Start::size_type> numOrigNNZ = std::nullopt);
|
||||
};
|
||||
|
||||
/// Accumulated coordinate format contributions that have not yet
|
||||
/// been added to the final CSR structure.
|
||||
Connections uncompressed_;
|
||||
|
||||
/// Canonical representation of unique inter-region flow rates.
|
||||
CSR csr_;
|
||||
};
|
||||
|
||||
}} // namespace Opm::utility
|
||||
|
||||
// Actual implementation of member functions in _impl.hpp file.
|
||||
#include <opm/common/utility/CSRGraphFromCoordinates_impl.hpp>
|
||||
|
||||
#endif // OPM_UTILITY_CSRGRAPHFROMCOORDINATES_HPP
|
||||
@@ -1,610 +0,0 @@
|
||||
/*
|
||||
Copyright 2016 SINTEF ICT, Applied Mathematics.
|
||||
Copyright 2016 Statoil ASA.
|
||||
Copyright 2022 Equinor ASA
|
||||
|
||||
This file is part of the Open Porous Media Project (OPM).
|
||||
|
||||
OPM is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
OPM is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with OPM. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <exception>
|
||||
#include <iterator>
|
||||
#include <optional>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Class Opm::utility::CSRGraphFromCoordinates::Connections
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
template <typename VertexID, bool TrackCompressedIdx, bool PermitSelfConnections>
|
||||
void
|
||||
Opm::utility::CSRGraphFromCoordinates<VertexID, TrackCompressedIdx, PermitSelfConnections>::
|
||||
Connections::add(const VertexID v1, const VertexID v2)
|
||||
{
|
||||
this->i_.push_back(v1);
|
||||
this->j_.push_back(v2);
|
||||
|
||||
this->max_i_ = std::max(this->max_i_.value_or(BaseVertexID{}), this->i_.back());
|
||||
this->max_j_ = std::max(this->max_j_.value_or(BaseVertexID{}), this->j_.back());
|
||||
}
|
||||
|
||||
template <typename VertexID, bool TrackCompressedIdx, bool PermitSelfConnections>
|
||||
void
|
||||
Opm::utility::CSRGraphFromCoordinates<VertexID, TrackCompressedIdx, PermitSelfConnections>::
|
||||
Connections::add(VertexID maxRowIdx,
|
||||
VertexID maxColIdx,
|
||||
const Neighbours& rows,
|
||||
const Neighbours& cols)
|
||||
{
|
||||
if (cols.size() != rows.size()) {
|
||||
throw std::invalid_argument {
|
||||
"Coordinate format column index table size does not match "
|
||||
"row index table size"
|
||||
};
|
||||
}
|
||||
|
||||
this->i_.insert(this->i_.end(), rows .begin(), rows .end());
|
||||
this->j_.insert(this->j_.end(), cols .begin(), cols .end());
|
||||
|
||||
this->max_i_ = std::max(this->max_i_.value_or(BaseVertexID{}), maxRowIdx);
|
||||
this->max_j_ = std::max(this->max_j_.value_or(BaseVertexID{}), maxColIdx);
|
||||
}
|
||||
|
||||
template <typename VertexID, bool TrackCompressedIdx, bool PermitSelfConnections>
|
||||
void
|
||||
Opm::utility::CSRGraphFromCoordinates<VertexID, TrackCompressedIdx, PermitSelfConnections>::
|
||||
Connections::clear()
|
||||
{
|
||||
this->j_.clear();
|
||||
this->i_.clear();
|
||||
|
||||
this->max_i_.reset();
|
||||
this->max_j_.reset();
|
||||
}
|
||||
|
||||
template <typename VertexID, bool TrackCompressedIdx, bool PermitSelfConnections>
|
||||
bool
|
||||
Opm::utility::CSRGraphFromCoordinates<VertexID, TrackCompressedIdx, PermitSelfConnections>::
|
||||
Connections::empty() const
|
||||
{
|
||||
return this->i_.empty();
|
||||
}
|
||||
|
||||
template <typename VertexID, bool TrackCompressedIdx, bool PermitSelfConnections>
|
||||
bool
|
||||
Opm::utility::CSRGraphFromCoordinates<VertexID, TrackCompressedIdx, PermitSelfConnections>::
|
||||
Connections::isValid() const
|
||||
{
|
||||
return this->i_.size() == this->j_.size();
|
||||
}
|
||||
|
||||
template <typename VertexID, bool TrackCompressedIdx, bool PermitSelfConnections>
|
||||
std::optional<typename Opm::utility::CSRGraphFromCoordinates<VertexID, TrackCompressedIdx, PermitSelfConnections>::BaseVertexID>
|
||||
Opm::utility::CSRGraphFromCoordinates<VertexID, TrackCompressedIdx, PermitSelfConnections>::
|
||||
Connections::maxRow() const
|
||||
{
|
||||
return this->max_i_;
|
||||
}
|
||||
|
||||
template <typename VertexID, bool TrackCompressedIdx, bool PermitSelfConnections>
|
||||
std::optional<typename Opm::utility::CSRGraphFromCoordinates<VertexID, TrackCompressedIdx, PermitSelfConnections>::BaseVertexID>
|
||||
Opm::utility::CSRGraphFromCoordinates<VertexID, TrackCompressedIdx, PermitSelfConnections>::
|
||||
Connections::maxCol() const
|
||||
{
|
||||
return this->max_j_;
|
||||
}
|
||||
|
||||
template <typename VertexID, bool TrackCompressedIdx, bool PermitSelfConnections>
|
||||
typename Opm::utility::CSRGraphFromCoordinates<VertexID, TrackCompressedIdx, PermitSelfConnections>::Neighbours::size_type
|
||||
Opm::utility::CSRGraphFromCoordinates<VertexID, TrackCompressedIdx, PermitSelfConnections>::
|
||||
Connections::numContributions() const
|
||||
{
|
||||
return this->i_.size();
|
||||
}
|
||||
|
||||
template <typename VertexID, bool TrackCompressedIdx, bool PermitSelfConnections>
|
||||
const typename Opm::utility::CSRGraphFromCoordinates<VertexID, TrackCompressedIdx, PermitSelfConnections>::Neighbours&
|
||||
Opm::utility::CSRGraphFromCoordinates<VertexID, TrackCompressedIdx, PermitSelfConnections>::
|
||||
Connections::rowIndices() const
|
||||
{
|
||||
return this->i_;
|
||||
}
|
||||
|
||||
template <typename VertexID, bool TrackCompressedIdx, bool PermitSelfConnections>
|
||||
const typename Opm::utility::CSRGraphFromCoordinates<VertexID, TrackCompressedIdx, PermitSelfConnections>::Neighbours&
|
||||
Opm::utility::CSRGraphFromCoordinates<VertexID, TrackCompressedIdx, PermitSelfConnections>::
|
||||
Connections::columnIndices() const
|
||||
{
|
||||
return this->j_;
|
||||
}
|
||||
|
||||
// =====================================================================
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Class Opm::utility::CSRGraphFromCoordinates::CSR
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
template <typename VertexID, bool TrackCompressedIdx, bool PermitSelfConnections>
|
||||
void
|
||||
Opm::utility::CSRGraphFromCoordinates<VertexID, TrackCompressedIdx, PermitSelfConnections>::
|
||||
CSR::merge(const Connections& conns,
|
||||
const Offset maxNumVertices,
|
||||
const bool expandExistingIdxMap)
|
||||
{
|
||||
const auto maxRow = conns.maxRow();
|
||||
|
||||
if (maxRow.has_value() &&
|
||||
(static_cast<Offset>(*maxRow) >= maxNumVertices))
|
||||
{
|
||||
throw std::invalid_argument {
|
||||
"Number of vertices in input graph (" +
|
||||
std::to_string(*maxRow) + ") "
|
||||
"exceeds maximum graph size implied by explicit size of "
|
||||
"adjacency matrix (" + std::to_string(maxNumVertices) + ')'
|
||||
};
|
||||
}
|
||||
|
||||
this->assemble(conns.rowIndices(), conns.columnIndices(),
|
||||
maxRow.value_or(BaseVertexID{0}),
|
||||
conns.maxCol().value_or(BaseVertexID{0}),
|
||||
expandExistingIdxMap);
|
||||
|
||||
this->compress(maxNumVertices);
|
||||
}
|
||||
|
||||
template <typename VertexID, bool TrackCompressedIdx, bool PermitSelfConnections>
|
||||
typename Opm::utility::CSRGraphFromCoordinates<VertexID, TrackCompressedIdx, PermitSelfConnections>::Offset
|
||||
Opm::utility::CSRGraphFromCoordinates<VertexID, TrackCompressedIdx, PermitSelfConnections>::
|
||||
CSR::numRows() const
|
||||
{
|
||||
return this->startPointers().empty()
|
||||
? 0 : this->startPointers().size() - 1;
|
||||
}
|
||||
|
||||
template <typename VertexID, bool TrackCompressedIdx, bool PermitSelfConnections>
|
||||
typename Opm::utility::CSRGraphFromCoordinates<VertexID, TrackCompressedIdx, PermitSelfConnections>::BaseVertexID
|
||||
Opm::utility::CSRGraphFromCoordinates<VertexID, TrackCompressedIdx, PermitSelfConnections>::
|
||||
CSR::maxRowID() const
|
||||
{
|
||||
return this->numRows_ - 1;
|
||||
}
|
||||
|
||||
template <typename VertexID, bool TrackCompressedIdx, bool PermitSelfConnections>
|
||||
typename Opm::utility::CSRGraphFromCoordinates<VertexID, TrackCompressedIdx, PermitSelfConnections>::BaseVertexID
|
||||
Opm::utility::CSRGraphFromCoordinates<VertexID, TrackCompressedIdx, PermitSelfConnections>::
|
||||
CSR::maxColID() const
|
||||
{
|
||||
return this->numCols_ - 1;
|
||||
}
|
||||
|
||||
template <typename VertexID, bool TrackCompressedIdx, bool PermitSelfConnections>
|
||||
const typename Opm::utility::CSRGraphFromCoordinates<VertexID, TrackCompressedIdx, PermitSelfConnections>::Start&
|
||||
Opm::utility::CSRGraphFromCoordinates<VertexID, TrackCompressedIdx, PermitSelfConnections>::
|
||||
CSR::startPointers() const
|
||||
{
|
||||
return this->ia_;
|
||||
}
|
||||
|
||||
template <typename VertexID, bool TrackCompressedIdx, bool PermitSelfConnections>
|
||||
const typename Opm::utility::CSRGraphFromCoordinates<VertexID, TrackCompressedIdx, PermitSelfConnections>::Neighbours&
|
||||
Opm::utility::CSRGraphFromCoordinates<VertexID, TrackCompressedIdx, PermitSelfConnections>::
|
||||
CSR::columnIndices() const
|
||||
{
|
||||
return this->ja_;
|
||||
}
|
||||
|
||||
template <typename VertexID, bool TrackCompressedIdx, bool PermitSelfConnections>
|
||||
typename Opm::utility::CSRGraphFromCoordinates<VertexID, TrackCompressedIdx, PermitSelfConnections>::Neighbours
|
||||
Opm::utility::CSRGraphFromCoordinates<VertexID, TrackCompressedIdx, PermitSelfConnections>::
|
||||
CSR::coordinateFormatRowIndices() const
|
||||
{
|
||||
auto rowIdx = Neighbours{};
|
||||
|
||||
if (this->ia_.empty()) {
|
||||
return rowIdx;
|
||||
}
|
||||
|
||||
rowIdx.reserve(this->ia_.back());
|
||||
|
||||
auto row = BaseVertexID{};
|
||||
|
||||
const auto m = this->ia_.size() - 1;
|
||||
for (auto i = 0*m; i < m; ++i, ++row) {
|
||||
const auto n = this->ia_[i + 1] - this->ia_[i + 0];
|
||||
|
||||
rowIdx.insert(rowIdx.end(), n, row);
|
||||
}
|
||||
|
||||
return rowIdx;
|
||||
}
|
||||
|
||||
template <typename VertexID, bool TrackCompressedIdx, bool PermitSelfConnections>
|
||||
void
|
||||
Opm::utility::CSRGraphFromCoordinates<VertexID, TrackCompressedIdx, PermitSelfConnections>::
|
||||
CSR::clear()
|
||||
{
|
||||
this->ia_.clear();
|
||||
this->ja_.clear();
|
||||
|
||||
if constexpr (TrackCompressedIdx) {
|
||||
this->compressedIdx_.clear();
|
||||
}
|
||||
|
||||
this->numRows_ = 0;
|
||||
this->numCols_ = 0;
|
||||
}
|
||||
|
||||
template <typename VertexID, bool TrackCompressedIdx, bool PermitSelfConnections>
|
||||
void
|
||||
Opm::utility::CSRGraphFromCoordinates<VertexID, TrackCompressedIdx, PermitSelfConnections>::
|
||||
CSR::assemble(const Neighbours& rows,
|
||||
const Neighbours& cols,
|
||||
const BaseVertexID maxRowIdx,
|
||||
const BaseVertexID maxColIdx,
|
||||
[[maybe_unused]] const bool expandExistingIdxMap)
|
||||
{
|
||||
[[maybe_unused]] auto compressedIdx = this->compressedIdx_;
|
||||
[[maybe_unused]] const auto numOrigNNZ = this->ja_.size();
|
||||
|
||||
auto i = this->coordinateFormatRowIndices();
|
||||
i.insert(i.end(), rows.begin(), rows.end());
|
||||
|
||||
auto j = this->ja_;
|
||||
j.insert(j.end(), cols.begin(), cols.end());
|
||||
|
||||
const auto thisNumRows = std::max(this->numRows_, maxRowIdx + 1);
|
||||
const auto thisNumCols = std::max(this->numCols_, maxColIdx + 1);
|
||||
|
||||
this->preparePushbackRowGrouping(thisNumRows, i);
|
||||
|
||||
this->groupAndTrackColumnIndicesByRow(i, j);
|
||||
|
||||
if constexpr (TrackCompressedIdx) {
|
||||
if (expandExistingIdxMap) {
|
||||
this->remapCompressedIndex(std::move(compressedIdx), numOrigNNZ);
|
||||
}
|
||||
}
|
||||
|
||||
this->numRows_ = thisNumRows;
|
||||
this->numCols_ = thisNumCols;
|
||||
}
|
||||
|
||||
template <typename VertexID, bool TrackCompressedIdx, bool PermitSelfConnections>
|
||||
void
|
||||
Opm::utility::CSRGraphFromCoordinates<VertexID, TrackCompressedIdx, PermitSelfConnections>::
|
||||
CSR::compress(const Offset maxNumVertices)
|
||||
{
|
||||
if (this->numRows() > maxNumVertices) {
|
||||
throw std::invalid_argument {
|
||||
"Number of vertices in input graph (" +
|
||||
std::to_string(this->numRows()) + ") "
|
||||
"exceeds maximum graph size implied by explicit size of "
|
||||
"adjacency matrix (" + std::to_string(maxNumVertices) + ')'
|
||||
};
|
||||
}
|
||||
|
||||
this->sortColumnIndicesPerRow();
|
||||
|
||||
// Must be called *after* sortColumnIndicesPerRow().
|
||||
this->condenseDuplicates();
|
||||
|
||||
const auto nRows = this->startPointers().size() - 1;
|
||||
if (nRows < maxNumVertices) {
|
||||
this->ia_.insert(this->ia_.end(),
|
||||
maxNumVertices - nRows,
|
||||
this->startPointers().back());
|
||||
}
|
||||
}
|
||||
|
||||
template <typename VertexID, bool TrackCompressedIdx, bool PermitSelfConnections>
|
||||
void
|
||||
Opm::utility::CSRGraphFromCoordinates<VertexID, TrackCompressedIdx, PermitSelfConnections>::
|
||||
CSR::sortColumnIndicesPerRow()
|
||||
{
|
||||
// Transposition is, in this context, effectively a linear time (O(nnz))
|
||||
// bucket insertion procedure. In other words transposing the structure
|
||||
// twice creates a structure with column indices in (ascendingly) sorted
|
||||
// order.
|
||||
|
||||
this->transpose();
|
||||
this->transpose();
|
||||
}
|
||||
|
||||
template <typename VertexID, bool TrackCompressedIdx, bool PermitSelfConnections>
|
||||
void
|
||||
Opm::utility::CSRGraphFromCoordinates<VertexID, TrackCompressedIdx, PermitSelfConnections>::
|
||||
CSR::condenseDuplicates()
|
||||
{
|
||||
// Note: Must be called *after* sortColumnIndicesPerRow().
|
||||
|
||||
const auto colIdx = this->ja_;
|
||||
auto end = colIdx.begin();
|
||||
|
||||
this->ja_.clear();
|
||||
|
||||
[[maybe_unused]] auto compressedIdx = this->compressedIdx_;
|
||||
if constexpr (TrackCompressedIdx) {
|
||||
this->compressedIdx_.clear();
|
||||
}
|
||||
|
||||
const auto numRows = this->ia_.size() - 1;
|
||||
for (auto row = 0*numRows; row < numRows; ++row) {
|
||||
auto begin = end;
|
||||
|
||||
std::advance(end, this->ia_[row + 1] - this->ia_[row + 0]);
|
||||
|
||||
const auto q = this->ja_.size();
|
||||
|
||||
this->condenseAndTrackUniqueColumnsForSingleRow(begin, end);
|
||||
|
||||
this->ia_[row + 0] = q;
|
||||
}
|
||||
|
||||
if constexpr (TrackCompressedIdx) {
|
||||
this->remapCompressedIndex(std::move(compressedIdx));
|
||||
}
|
||||
|
||||
// Record final table sizes.
|
||||
this->ia_.back() = this->ja_.size();
|
||||
}
|
||||
|
||||
template <typename VertexID, bool TrackCompressedIdx, bool PermitSelfConnections>
|
||||
void
|
||||
Opm::utility::CSRGraphFromCoordinates<VertexID, TrackCompressedIdx, PermitSelfConnections>::
|
||||
CSR::preparePushbackRowGrouping(const int numRows,
|
||||
const Neighbours& rowIdx)
|
||||
{
|
||||
assert (numRows >= 0);
|
||||
|
||||
this->ia_.assign(numRows + 1, 0);
|
||||
|
||||
// Count number of neighbouring vertices for each row. Accumulate in
|
||||
// "next" bin since we're positioning the end pointers.
|
||||
for (const auto& row : rowIdx) {
|
||||
this->ia_[row + 1] += 1;
|
||||
}
|
||||
|
||||
// Position "end" pointers.
|
||||
//
|
||||
// After this loop, ia_[i + 1] points to the *start* of the range of the
|
||||
// column indices/neighbouring vertices of vertex 'i'. This, in turn,
|
||||
// enables using the statement ja_[ia_[i+1]++] = v in groupAndTrack()
|
||||
// to insert vertex 'v' as a neighbour, at the end of the range of known
|
||||
// neighbours, *and* advance the end pointer of row/vertex 'i'. We use
|
||||
// ia_[0] as an accumulator for the total number of neighbouring
|
||||
// vertices in the graph.
|
||||
//
|
||||
// Note index range: 1..numRows inclusive.
|
||||
for (typename Start::size_type i = 1, n = numRows; i <= n; ++i) {
|
||||
this->ia_[0] += this->ia_[i];
|
||||
this->ia_[i] = this->ia_[0] - this->ia_[i];
|
||||
}
|
||||
|
||||
assert (this->ia_[0] == rowIdx.size());
|
||||
}
|
||||
|
||||
template <typename VertexID, bool TrackCompressedIdx, bool PermitSelfConnections>
|
||||
void
|
||||
Opm::utility::CSRGraphFromCoordinates<VertexID, TrackCompressedIdx, PermitSelfConnections>::
|
||||
CSR::groupAndTrackColumnIndicesByRow(const Neighbours& rowIdx,
|
||||
const Neighbours& colIdx)
|
||||
{
|
||||
assert (this->ia_[0] == rowIdx.size());
|
||||
|
||||
const auto nnz = rowIdx.size();
|
||||
|
||||
this->ja_.resize(nnz);
|
||||
|
||||
if constexpr (TrackCompressedIdx) {
|
||||
this->compressedIdx_.clear();
|
||||
this->compressedIdx_.reserve(nnz);
|
||||
}
|
||||
|
||||
// Group/insert column indices according to their associate vertex/row
|
||||
// index.
|
||||
//
|
||||
// At the start of the loop the end pointers ia_[i+1], formed in
|
||||
// preparePushback(), are positioned at the *start* of the column index
|
||||
// range associated to vertex 'i'. After this loop all vertices
|
||||
// neighbouring vertex 'i' will be placed consecutively, in order of
|
||||
// appearance, into ja_. Furthermore, the row pointers ia_ will have
|
||||
// their final position.
|
||||
//
|
||||
// The statement ja_[ia_[i+1]++] = v, split into two statements using
|
||||
// the helper object 'k', inserts 'v' as a neighbouring vertex of vertex
|
||||
// 'i' *and* advances the end pointer ia_[i+1] of that vertex. We use
|
||||
// and maintain the invariant that ia_[i+1] at all times records the
|
||||
// insertion point of the next neighbouring vertex of vertex 'i'. When
|
||||
// the list of neighbouring vertices for vertex 'i' has been exhausted,
|
||||
// ia_[i+1] will hold the start position for in ja_ for vertex i+1.
|
||||
for (auto nz = 0*nnz; nz < nnz; ++nz) {
|
||||
const auto k = this->ia_[rowIdx[nz] + 1] ++;
|
||||
|
||||
this->ja_[k] = colIdx[nz];
|
||||
|
||||
if constexpr (TrackCompressedIdx) {
|
||||
this->compressedIdx_.push_back(k);
|
||||
}
|
||||
}
|
||||
|
||||
this->ia_[0] = 0;
|
||||
}
|
||||
|
||||
template <typename VertexID, bool TrackCompressedIdx, bool PermitSelfConnections>
|
||||
void
|
||||
Opm::utility::CSRGraphFromCoordinates<VertexID, TrackCompressedIdx, PermitSelfConnections>::
|
||||
CSR::transpose()
|
||||
{
|
||||
[[maybe_unused]] auto compressedIdx = this->compressedIdx_;
|
||||
|
||||
{
|
||||
const auto rowIdx = this->coordinateFormatRowIndices();
|
||||
const auto colIdx = this->ja_;
|
||||
|
||||
this->preparePushbackRowGrouping(this->numCols_, colIdx);
|
||||
|
||||
// Note parameter order. Transposition switches role of rows and
|
||||
// columns.
|
||||
this->groupAndTrackColumnIndicesByRow(colIdx, rowIdx);
|
||||
}
|
||||
|
||||
if constexpr (TrackCompressedIdx) {
|
||||
this->remapCompressedIndex(std::move(compressedIdx));
|
||||
}
|
||||
|
||||
std::swap(this->numRows_, this->numCols_);
|
||||
}
|
||||
|
||||
template <typename VertexID, bool TrackCompressedIdx, bool PermitSelfConnections>
|
||||
void
|
||||
Opm::utility::CSRGraphFromCoordinates<VertexID, TrackCompressedIdx, PermitSelfConnections>::
|
||||
CSR::condenseAndTrackUniqueColumnsForSingleRow(typename Neighbours::const_iterator begin,
|
||||
typename Neighbours::const_iterator end)
|
||||
{
|
||||
// We assume that we're only called *after* sortColumnIndicesPerRow()
|
||||
// whence duplicate elements appear consecutively in [begin, end).
|
||||
//
|
||||
// Note: This is essentially the same as std::unique(begin, end) save
|
||||
// for the return value and the fact that we additionally record the
|
||||
// 'compressedIdx_' mapping. That mapping enables subsequent, decoupled
|
||||
// accumulation of the 'sa_' contributions.
|
||||
|
||||
while (begin != end) {
|
||||
// Note: Order of ja_ and compressedIdx_ matters here.
|
||||
|
||||
if constexpr (TrackCompressedIdx) {
|
||||
this->compressedIdx_.push_back(this->ja_.size());
|
||||
}
|
||||
|
||||
this->ja_.push_back(*begin);
|
||||
|
||||
auto next_unique =
|
||||
std::find_if(begin, end, [last = this->ja_.back()]
|
||||
(const auto j) { return j != last; });
|
||||
|
||||
if constexpr (TrackCompressedIdx) {
|
||||
// Number of duplicate elements in [begin, next_unique).
|
||||
const auto ndup = std::distance(begin, next_unique);
|
||||
|
||||
if (ndup > 1) {
|
||||
// Insert ndup - 1 copies of .back() to represent the
|
||||
// duplicate pairs in [begin, next_unique). We subtract one
|
||||
// to account for .push_back() above representing *begin.
|
||||
this->compressedIdx_.insert(this->compressedIdx_.end(),
|
||||
ndup - 1,
|
||||
this->compressedIdx_.back());
|
||||
}
|
||||
}
|
||||
|
||||
begin = next_unique;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename VertexID, bool TrackCompressedIdx, bool PermitSelfConnections>
|
||||
void
|
||||
Opm::utility::CSRGraphFromCoordinates<VertexID, TrackCompressedIdx, PermitSelfConnections>::CSR::
|
||||
remapCompressedIndex([[maybe_unused]] Start&& compressedIdx,
|
||||
[[maybe_unused]] std::optional<typename Start::size_type> numOrig)
|
||||
{
|
||||
if constexpr (TrackCompressedIdx) {
|
||||
for (auto& i : compressedIdx) {
|
||||
i = this->compressedIdx_[i];
|
||||
}
|
||||
|
||||
if (numOrig.has_value() && (*numOrig < this->compressedIdx_.size())) {
|
||||
// Client called add() after compress(). Remap existing portion
|
||||
// of compressedIdx (above), and append new entries (here).
|
||||
compressedIdx
|
||||
.insert(compressedIdx.end(),
|
||||
this->compressedIdx_.begin() + *numOrig,
|
||||
this->compressedIdx_.end());
|
||||
}
|
||||
|
||||
this->compressedIdx_.swap(compressedIdx);
|
||||
}
|
||||
}
|
||||
|
||||
// =====================================================================
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Class Opm::utility::CSRGraphFromCoordinates
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
template <typename VertexID, bool TrackCompressedIdx, bool PermitSelfConnections>
|
||||
void Opm::utility::CSRGraphFromCoordinates<VertexID, TrackCompressedIdx, PermitSelfConnections>::clear()
|
||||
{
|
||||
this->uncompressed_.clear();
|
||||
this->csr_.clear();
|
||||
}
|
||||
|
||||
template <typename VertexID, bool TrackCompressedIdx, bool PermitSelfConnections>
|
||||
void
|
||||
Opm::utility::CSRGraphFromCoordinates<VertexID, TrackCompressedIdx, PermitSelfConnections>::
|
||||
addConnection(const VertexID v1, const VertexID v2)
|
||||
{
|
||||
if ((v1 < 0) || (v2 < 0)) {
|
||||
throw std::invalid_argument {
|
||||
"Vertex IDs must be non-negative. Got (v1,v2) = ("
|
||||
+ std::to_string(v1) + ", " + std::to_string(v2)
|
||||
+ ')'
|
||||
};
|
||||
}
|
||||
|
||||
if constexpr (! PermitSelfConnections) {
|
||||
if (v1 == v2) {
|
||||
// Ignore self connections.
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
this->uncompressed_.add(v1, v2);
|
||||
}
|
||||
|
||||
template <typename VertexID, bool TrackCompressedIdx, bool PermitSelfConnections>
|
||||
void
|
||||
Opm::utility::CSRGraphFromCoordinates<VertexID, TrackCompressedIdx, PermitSelfConnections>::
|
||||
compress(const Offset maxNumVertices, const bool expandExistingIdxMap)
|
||||
{
|
||||
if (! this->uncompressed_.isValid()) {
|
||||
throw std::logic_error {
|
||||
"Cannot compress invalid connection list"
|
||||
};
|
||||
}
|
||||
|
||||
this->csr_.merge(this->uncompressed_, maxNumVertices, expandExistingIdxMap);
|
||||
|
||||
this->uncompressed_.clear();
|
||||
}
|
||||
|
||||
template <typename VertexID, bool TrackCompressedIdx, bool PermitSelfConnections>
|
||||
typename Opm::utility::CSRGraphFromCoordinates<VertexID, TrackCompressedIdx, PermitSelfConnections>::Offset
|
||||
Opm::utility::CSRGraphFromCoordinates<VertexID, TrackCompressedIdx, PermitSelfConnections>::numVertices() const
|
||||
{
|
||||
return this->csr_.numRows();
|
||||
}
|
||||
|
||||
template <typename VertexID, bool TrackCompressedIdx, bool PermitSelfConnections>
|
||||
typename Opm::utility::CSRGraphFromCoordinates<VertexID, TrackCompressedIdx, PermitSelfConnections>::Offset
|
||||
Opm::utility::CSRGraphFromCoordinates<VertexID, TrackCompressedIdx, PermitSelfConnections>::numEdges() const
|
||||
{
|
||||
const auto& ia = this->startPointers();
|
||||
|
||||
return ia.empty() ? 0 : ia.back();
|
||||
}
|
||||
@@ -22,80 +22,86 @@
|
||||
#define OPM_LINEARINTERPOLATION_HEADER_INCLUDED
|
||||
|
||||
|
||||
#include <algorithm>
|
||||
#include <tuple>
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
|
||||
namespace Opm
|
||||
{
|
||||
|
||||
//! \brief Returns an index in an ordered table such that x is between
|
||||
//! table[j] and table[j+1].
|
||||
//! \details If x is out of bounds, it returns a clamped index
|
||||
inline int tableIndex(const std::vector<double>& table, double x)
|
||||
{
|
||||
if (table.size() < 2)
|
||||
return 0;
|
||||
|
||||
const auto lower = std::lower_bound(table.begin(), table.end(), x);
|
||||
|
||||
if (lower == table.end())
|
||||
return table.size()-2;
|
||||
else if (lower == table.begin())
|
||||
return 0;
|
||||
else
|
||||
return std::distance(table.begin(), lower)-1;
|
||||
}
|
||||
|
||||
inline std::pair<double, int>
|
||||
linearInterpolationSlope(const std::vector<double>& xv,
|
||||
const std::vector<double>& yv,
|
||||
const double x)
|
||||
{
|
||||
const auto i = Opm::tableIndex(xv, x);
|
||||
return { (yv[i + 1] - yv[i]) / (xv[i + 1] - xv[i]), i };
|
||||
}
|
||||
|
||||
|
||||
inline double linearInterpolationDerivative(const std::vector<double>& xv,
|
||||
const std::vector<double>& yv, double x)
|
||||
{
|
||||
// Extrapolates if x is outside xv
|
||||
return linearInterpolationSlope(xv, yv, x).first;
|
||||
}
|
||||
|
||||
inline double linearInterpolation(const std::vector<double>& xv,
|
||||
const std::vector<double>& yv, double x)
|
||||
{
|
||||
// Extrapolates if x is outside xv
|
||||
const auto& [t, i] = linearInterpolationSlope(xv, yv, x);
|
||||
return t * (x - xv[i]) + yv[i];
|
||||
}
|
||||
|
||||
inline double linearInterpolationNoExtrapolation(const std::vector<double>& xv,
|
||||
const std::vector<double>& yv, double x)
|
||||
{
|
||||
// Return end values if x is outside xv
|
||||
if (x < xv.front()) {
|
||||
return yv.front();
|
||||
}
|
||||
if (x > xv.back()) {
|
||||
return yv.back();
|
||||
inline int tableIndex(const std::vector<double>& table, double x)
|
||||
{
|
||||
// Returns an index in an ordered table such that x is between
|
||||
// table[j] and table[j+1]. If x is out of range, first or last
|
||||
// interval is returned; Binary search.
|
||||
int n = table.size() - 1;
|
||||
if (n < 2) {
|
||||
return 0;
|
||||
}
|
||||
int jl = 0;
|
||||
int ju = n;
|
||||
bool ascend = (table[n] > table[0]);
|
||||
while (ju - jl > 1) {
|
||||
int jm = (ju + jl)/2; // Compute a midpoint
|
||||
if ( (x >= table[jm]) == ascend) {
|
||||
jl = jm; // Replace lower limit
|
||||
} else {
|
||||
ju = jm; // Replace upper limit
|
||||
}
|
||||
}
|
||||
return jl;
|
||||
}
|
||||
|
||||
|
||||
inline double linearInterpolationDerivative(const std::vector<double>& xv,
|
||||
const std::vector<double>& yv, double x)
|
||||
{
|
||||
// Extrapolates if x is outside xv
|
||||
int ix1 = tableIndex(xv, x);
|
||||
int ix2 = ix1 + 1;
|
||||
return (yv[ix2] - yv[ix1])/(xv[ix2] - xv[ix1]);
|
||||
}
|
||||
|
||||
inline double linearInterpolation(const std::vector<double>& xv,
|
||||
const std::vector<double>& yv, double x)
|
||||
{
|
||||
// Extrapolates if x is outside xv
|
||||
int ix1 = tableIndex(xv, x);
|
||||
int ix2 = ix1 + 1;
|
||||
return (yv[ix2] - yv[ix1])/(xv[ix2] - xv[ix1])*(x - xv[ix1]) + yv[ix1];
|
||||
}
|
||||
|
||||
inline double linearInterpolationNoExtrapolation(const std::vector<double>& xv,
|
||||
const std::vector<double>& yv, double x)
|
||||
{
|
||||
// Return end values if x is outside xv
|
||||
if (x < xv.front()) {
|
||||
return yv.front();
|
||||
}
|
||||
if (x > xv.back()) {
|
||||
return yv.back();
|
||||
}
|
||||
|
||||
int ix1 = tableIndex(xv, x);
|
||||
int ix2 = ix1 + 1;
|
||||
return (yv[ix2] - yv[ix1])/(xv[ix2] - xv[ix1])*(x - xv[ix1]) + yv[ix1];
|
||||
}
|
||||
|
||||
inline double linearInterpolation(const std::vector<double>& xv,
|
||||
const std::vector<double>& yv,
|
||||
double x, int& ix1)
|
||||
{
|
||||
// Extrapolates if x is outside xv
|
||||
ix1 = tableIndex(xv, x);
|
||||
int ix2 = ix1 + 1;
|
||||
return (yv[ix2] - yv[ix1])/(xv[ix2] - xv[ix1])*(x - xv[ix1]) + yv[ix1];
|
||||
}
|
||||
|
||||
return linearInterpolation(xv, yv, x);
|
||||
}
|
||||
|
||||
inline double linearInterpolation(const std::vector<double>& xv,
|
||||
const std::vector<double>& yv,
|
||||
double x, int& ix1)
|
||||
{
|
||||
// Extrapolates if x is outside xv
|
||||
double t;
|
||||
std::tie(t, ix1) = linearInterpolationSlope(xv, yv, x);
|
||||
return t * (x - xv[ix1]) + yv[ix1];
|
||||
}
|
||||
|
||||
} // namespace Opm
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
#endif // OPM_LINEARINTERPOLATION_HEADER_INCLUDED
|
||||
|
||||
@@ -21,7 +21,6 @@
|
||||
#define OPM_ECLIPSE_CONFIG_HPP
|
||||
|
||||
#include <opm/input/eclipse/EclipseState/InitConfig/InitConfig.hpp>
|
||||
#include <opm/input/eclipse/EclipseState/IOConfig/FIPConfig.hpp>
|
||||
#include <opm/input/eclipse/EclipseState/IOConfig/IOConfig.hpp>
|
||||
|
||||
namespace Opm {
|
||||
@@ -33,9 +32,7 @@ namespace Opm {
|
||||
public:
|
||||
EclipseConfig() = default;
|
||||
explicit EclipseConfig(const Deck& deck);
|
||||
EclipseConfig(const InitConfig& initConfig,
|
||||
const FIPConfig& fip_conf,
|
||||
const IOConfig& io_conf);
|
||||
EclipseConfig(const InitConfig& initConfig, const IOConfig& io_conf);
|
||||
|
||||
static EclipseConfig serializationTestObject();
|
||||
|
||||
@@ -43,7 +40,6 @@ namespace Opm {
|
||||
IOConfig& io();
|
||||
const IOConfig& io() const;
|
||||
const InitConfig& init() const;
|
||||
const FIPConfig& fip() const;
|
||||
|
||||
bool operator==(const EclipseConfig& data) const;
|
||||
static bool rst_cmp(const EclipseConfig& full_config, const EclipseConfig& rst_config);
|
||||
@@ -51,13 +47,11 @@ namespace Opm {
|
||||
void serializeOp(Serializer& serializer)
|
||||
{
|
||||
serializer(m_initConfig);
|
||||
serializer(fip_config);
|
||||
serializer(io_config);
|
||||
}
|
||||
|
||||
private:
|
||||
InitConfig m_initConfig;
|
||||
FIPConfig fip_config;
|
||||
IOConfig io_config;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -23,7 +23,6 @@
|
||||
#include <cstddef>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <optional>
|
||||
|
||||
#include <opm/input/eclipse/EclipseState/Aquifer/AquiferConfig.hpp>
|
||||
#include <opm/input/eclipse/EclipseState/EclipseConfig.hpp>
|
||||
@@ -51,7 +50,6 @@ namespace Opm {
|
||||
|
||||
namespace Opm { namespace RestartIO {
|
||||
class RstAquifer;
|
||||
class RstNetwork;
|
||||
}} // namespace Opm::RestartIO
|
||||
|
||||
namespace Opm {
|
||||
@@ -126,9 +124,6 @@ namespace Opm {
|
||||
// When we know and decide to handle the same for AQUFETP and AQUCT, this part will be refactored
|
||||
void appendAqufluxSchedule(const std::unordered_set<int>& ids);
|
||||
|
||||
void loadRestartNetworkPressures(const RestartIO::RstNetwork& network);
|
||||
const std::optional<std::map<std::string, double> >& getRestartNetworkPressures() const { return this->m_restart_network_pressures; }
|
||||
|
||||
template<class Serializer>
|
||||
void serializeOp(Serializer& serializer)
|
||||
{
|
||||
@@ -188,8 +183,6 @@ namespace Opm {
|
||||
|
||||
std::string m_title{};
|
||||
FaultCollection m_faults{};
|
||||
|
||||
std::optional<std::map<std::string, double> > m_restart_network_pressures{std::nullopt};
|
||||
};
|
||||
} // namespace Opm
|
||||
|
||||
|
||||
@@ -130,8 +130,7 @@ namespace ALIAS {
|
||||
|
||||
|
||||
namespace GRID {
|
||||
static const std::unordered_map<std::string, keyword_info<double>> double_keywords = {{"DISPERC",keyword_info<double>{}.unit_string("Length")},
|
||||
{"MULTPV", keyword_info<double>{}.init(1.0)},
|
||||
static const std::unordered_map<std::string, keyword_info<double>> double_keywords = {{"MULTPV", keyword_info<double>{}.init(1.0)},
|
||||
{"NTG", keyword_info<double>{}.init(1.0)},
|
||||
{"PORO", keyword_info<double>{}.distribute_top(true)},
|
||||
{"PERMX", keyword_info<double>{}.unit_string("Permeability").distribute_top(true)},
|
||||
|
||||
@@ -1,94 +0,0 @@
|
||||
/*
|
||||
Copyright 2023 SINTEF Digital
|
||||
|
||||
This file is part of the Open Porous Media project (OPM).
|
||||
|
||||
OPM is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
OPM is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with OPM. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef OPM_FIP_CONFIG_HPP
|
||||
#define OPM_FIP_CONFIG_HPP
|
||||
|
||||
#include <bitset>
|
||||
|
||||
namespace Opm {
|
||||
|
||||
class Deck;
|
||||
class DeckKeyword;
|
||||
class RPTConfig;
|
||||
|
||||
//! \brief Class holding FIP configuration from RPTSOL/RPTSCHED keyword.
|
||||
class FIPConfig {
|
||||
public:
|
||||
//! \brief Enumeration of FIP report outputs.
|
||||
enum class OutputField {
|
||||
FIELD = 0, //!< Whole field
|
||||
FIPNUM = 1, //!< FIPNUM regions
|
||||
FIP = 2, //!< FIP defined regions
|
||||
FOAM_FIELD = 3, //!< Foam field report
|
||||
FOAM_REGION = 4, //!< Foam region report
|
||||
POLYMER_FIELD = 5, //!< Polymer field report
|
||||
POLYMER_REGION = 6, //!< Polymer region report
|
||||
RESV = 7, //!< RESV report
|
||||
SOLVENT_FIELD = 8, //!< Solvent field report
|
||||
SOLVENT_REGION = 9, //!< Solvent region report
|
||||
TEMPERATURE_FIELD = 10, //!< Temperature field report
|
||||
TEMPERATURE_REGION = 11, //!< Temperature region report
|
||||
SURF_FIELD = 12, //!< Surfacant field report
|
||||
SURF_REGION = 13, //!< Surfacant region report
|
||||
TRACER_FIELD = 14, //!< Tracer field report
|
||||
TRACER_REGION = 15, //!< Tracer region report
|
||||
VE = 16, //!< VE (oil, water, gas) zone report
|
||||
NUM_FIP_REPORT = 17, //!< Number of configuration flags
|
||||
};
|
||||
|
||||
//! \brief Default constructor.
|
||||
FIPConfig() = default;
|
||||
|
||||
//! \brief Construct from RPTSOL keyword if deck holds one.
|
||||
explicit FIPConfig(const Deck& deck);
|
||||
|
||||
//! \brief Construct from given keyword (RPTSOL or RPTSCHED).
|
||||
explicit FIPConfig(const DeckKeyword& keyword);
|
||||
|
||||
//! \brief Construct from given RTPConfig.
|
||||
explicit FIPConfig(const RPTConfig& rptConfig);
|
||||
|
||||
//! \brief Returns a test object used for serialization tests.
|
||||
static FIPConfig serializationTestObject();
|
||||
|
||||
//! \brief (De-)serialization handler.
|
||||
template<class Serializer>
|
||||
void serializeOp(Serializer& serializer)
|
||||
{
|
||||
serializer(m_flags);
|
||||
}
|
||||
|
||||
//! \brief Query if FIP output is enabled for a given field.
|
||||
bool output(OutputField field) const;
|
||||
|
||||
//! \brief Comparison operator.
|
||||
bool operator==(const FIPConfig& rhs) const;
|
||||
|
||||
private:
|
||||
//! \brief Initialize flags based on mnemonics in a RPTConfig instance.
|
||||
void parseRPT(const RPTConfig& rptConfig);
|
||||
|
||||
//! \brief Bitset holding enable status for fields
|
||||
std::bitset<static_cast<int>(OutputField::NUM_FIP_REPORT)> m_flags = {};
|
||||
};
|
||||
|
||||
} //namespace Opm
|
||||
|
||||
#endif
|
||||
@@ -71,7 +71,6 @@ public:
|
||||
std::size_t num_rock_tables() const;
|
||||
Hysteresis hysteresis_mode() const;
|
||||
bool water_compaction() const;
|
||||
bool dispersion() const;
|
||||
|
||||
bool operator==(const RockConfig& other) const;
|
||||
|
||||
@@ -84,7 +83,6 @@ public:
|
||||
serializer(num_tables);
|
||||
serializer(m_water_compaction);
|
||||
serializer(hyst_mode);
|
||||
serializer(m_dispersion);
|
||||
}
|
||||
|
||||
private:
|
||||
@@ -94,7 +92,6 @@ private:
|
||||
std::size_t num_tables = 0;
|
||||
bool m_water_compaction = false;
|
||||
Hysteresis hyst_mode = Hysteresis::REVERS;
|
||||
bool m_dispersion = false;
|
||||
};
|
||||
|
||||
} //namespace Opm
|
||||
|
||||
@@ -126,13 +126,6 @@ namespace Opm
|
||||
* The well has been affected in an ACTIONX keyword.
|
||||
*/
|
||||
ACTIONX_WELL_EVENT = (1 << 20),
|
||||
|
||||
/*
|
||||
* Some SCHEDULE keywords can set a well to be OPEN to open a previously STOPped or SHUT well.
|
||||
* The well is SHUT/STOP due to various causes (SCHEDULE, economical, physical, etc.)
|
||||
* For now, the WELOPEN, WCONPROD and WCONINJE keywords are considered with this event
|
||||
*/
|
||||
REQUEST_OPEN_WELL = (1 << 21),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -24,11 +24,8 @@
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <optional>
|
||||
|
||||
#include <opm/input/eclipse/Schedule/MSW/icd.hpp>
|
||||
#include <opm/input/eclipse/Deck/UDAValue.hpp>
|
||||
#include <opm/input/eclipse/Schedule/SummaryState.hpp>
|
||||
|
||||
|
||||
namespace Opm {
|
||||
@@ -37,24 +34,11 @@ namespace Opm {
|
||||
class DeckKeyword;
|
||||
class Segment;
|
||||
|
||||
struct ValveUDAEval {
|
||||
const SummaryState& summary_state;
|
||||
const std::string& well_name;
|
||||
const size_t segment_number;
|
||||
|
||||
ValveUDAEval(const SummaryState& summary_state_,
|
||||
const std::string& well_name_,
|
||||
const size_t segment_number_);
|
||||
|
||||
double value(const UDAValue& value, const double udq_default = 0.0) const;
|
||||
};
|
||||
|
||||
|
||||
class Valve {
|
||||
public:
|
||||
|
||||
Valve();
|
||||
explicit Valve(const DeckRecord& record, const double udq_default = 0.0);
|
||||
explicit Valve(const DeckRecord& record);
|
||||
Valve(double conFlowCoeff,
|
||||
double conCrossA,
|
||||
double conMaxCrossA,
|
||||
@@ -70,12 +54,11 @@ namespace Opm {
|
||||
// [
|
||||
// "WELL1" : [<seg1, valv1>, <seg2, valv2> ...]
|
||||
// ....
|
||||
static std::map<std::string, std::vector<std::pair<int, Valve> > > fromWSEGVALV(const DeckKeyword& keyword, const double udq_default = 0.0);
|
||||
static std::map<std::string, std::vector<std::pair<int, Valve> > > fromWSEGVALV(const DeckKeyword& keyword);
|
||||
|
||||
// parameters for constriction pressure loss
|
||||
double conFlowCoefficient() const;
|
||||
double conCrossArea(const std::optional<const ValveUDAEval>& uda_eval = std::nullopt) const;
|
||||
inline double conCrossAreaValue() const { return m_con_cross_area_value; }
|
||||
double conCrossArea() const;
|
||||
double conMaxCrossArea() const;
|
||||
double pipeDiameter() const;
|
||||
double pipeRoughness() const;
|
||||
@@ -102,20 +85,17 @@ namespace Opm {
|
||||
{
|
||||
serializer(m_con_flow_coeff);
|
||||
serializer(m_con_cross_area);
|
||||
serializer(m_con_cross_area_value);
|
||||
serializer(m_con_max_cross_area);
|
||||
serializer(m_pipe_additional_length);
|
||||
serializer(m_pipe_diameter);
|
||||
serializer(m_pipe_roughness);
|
||||
serializer(m_pipe_cross_area);
|
||||
serializer(m_status);
|
||||
serializer(m_udq_default);
|
||||
}
|
||||
|
||||
private:
|
||||
double m_con_flow_coeff;
|
||||
UDAValue m_con_cross_area;
|
||||
mutable double m_con_cross_area_value;
|
||||
double m_con_cross_area;
|
||||
double m_con_max_cross_area;
|
||||
|
||||
double m_pipe_additional_length;
|
||||
@@ -123,8 +103,6 @@ namespace Opm {
|
||||
double m_pipe_roughness;
|
||||
double m_pipe_cross_area;
|
||||
ICDStatus m_status;
|
||||
|
||||
double m_udq_default{0.0};
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -45,7 +45,6 @@ public:
|
||||
|
||||
const std::string& downtree_node() const;
|
||||
const std::string& uptree_node() const;
|
||||
void set_uptree_node(const std::string& new_uptree_node);
|
||||
std::optional<int> vfp_table() const;
|
||||
AlqEQ alq_eq() const;
|
||||
std::optional<double> alq_value() const;
|
||||
|
||||
@@ -25,7 +25,6 @@
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <functional>
|
||||
|
||||
#include <opm/input/eclipse/Schedule/Network/Branch.hpp>
|
||||
#include <opm/input/eclipse/Schedule/Network/Node.hpp>
|
||||
@@ -38,15 +37,12 @@ class ExtNetwork {
|
||||
public:
|
||||
ExtNetwork() = default;
|
||||
bool active() const;
|
||||
bool is_standard_network() const;
|
||||
void set_standard_network(bool is_standard_network);
|
||||
void add_branch(Branch branch);
|
||||
void add_or_replace_branch(Branch branch);
|
||||
void drop_branch(const std::string& uptree_node, const std::string& downtree_node);
|
||||
bool has_node(const std::string& name) const;
|
||||
void update_node(Node node);
|
||||
const Node& node(const std::string& name) const;
|
||||
std::vector<std::reference_wrapper<const Node>> roots() const;
|
||||
const Node& root() const;
|
||||
std::vector<Branch> downtree_branches(const std::string& node) const;
|
||||
std::vector<const Branch*> branches() const;
|
||||
std::optional<Branch> uptree_branch(const std::string& node) const;
|
||||
@@ -62,14 +58,12 @@ public:
|
||||
serializer(m_branches);
|
||||
serializer(insert_indexed_node_names);
|
||||
serializer(m_nodes);
|
||||
serializer(m_is_standard_network);
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<Branch> m_branches;
|
||||
std::vector<std::string> insert_indexed_node_names;
|
||||
std::map<std::string, Node> m_nodes;
|
||||
bool m_is_standard_network{false};
|
||||
bool has_indexed_node_name(const std::string& name) const;
|
||||
void add_indexed_node_name(std::string name);
|
||||
};
|
||||
|
||||
@@ -61,7 +61,6 @@ namespace Opm
|
||||
class GTNode;
|
||||
class GuideRateConfig;
|
||||
class GuideRateModel;
|
||||
class HandlerContext;
|
||||
enum class InputErrorAction;
|
||||
class ParseContext;
|
||||
class Python;
|
||||
@@ -77,7 +76,6 @@ namespace Opm
|
||||
class WellMatcher;
|
||||
enum class WellProducerCMode;
|
||||
enum class WellStatus;
|
||||
class WelSegsSet;
|
||||
class WellTestConfig;
|
||||
|
||||
namespace RestartIO { struct RstState; }
|
||||
@@ -134,6 +132,28 @@ namespace Opm
|
||||
|
||||
class Schedule {
|
||||
public:
|
||||
|
||||
struct PairComp
|
||||
{
|
||||
bool operator()(const std::pair<std::string,KeywordLocation>& pair,
|
||||
const std::string& str) const
|
||||
{
|
||||
return std::get<0>(pair) < str;
|
||||
}
|
||||
bool operator()(const std::pair<std::string,KeywordLocation>& pair1,
|
||||
const std::pair<std::string,KeywordLocation>& pair2) const
|
||||
{
|
||||
return std::get<0>(pair1) < std::get<0>(pair2);
|
||||
}
|
||||
bool operator()(const std::string& str,
|
||||
const std::pair<std::string,KeywordLocation>& pair) const
|
||||
{
|
||||
return str < std::get<0>(pair);
|
||||
}
|
||||
};
|
||||
|
||||
using WelSegsSet = std::set<std::pair<std::string,KeywordLocation>,PairComp>;
|
||||
|
||||
Schedule() = default;
|
||||
explicit Schedule(std::shared_ptr<const Python> python_handle);
|
||||
Schedule(const Deck& deck,
|
||||
@@ -347,7 +367,6 @@ namespace Opm
|
||||
this->template pack_unpack<GasLiftOpt>(serializer);
|
||||
this->template pack_unpack<RFTConfig>(serializer);
|
||||
this->template pack_unpack<RSTConfig>(serializer);
|
||||
this->template pack_unpack<ScheduleState::BHPDefaults>(serializer);
|
||||
|
||||
this->template pack_unpack_map<int, VFPProdTable>(serializer);
|
||||
this->template pack_unpack_map<int, VFPInjTable>(serializer);
|
||||
@@ -490,6 +509,71 @@ namespace Opm
|
||||
void dump_deck(std::ostream& os) const;
|
||||
|
||||
private:
|
||||
struct HandlerContext {
|
||||
|
||||
const ScheduleBlock& block;
|
||||
const DeckKeyword& keyword;
|
||||
const std::size_t currentStep;
|
||||
const std::vector<std::string>& matching_wells;
|
||||
const bool actionx_mode;
|
||||
const ParseContext& parseContext;
|
||||
ErrorGuard& errors;
|
||||
SimulatorUpdate* sim_update{nullptr};
|
||||
const std::unordered_map<std::string, double>* target_wellpi{nullptr};
|
||||
std::unordered_map<std::string, double>* wpimult_global_factor{nullptr};
|
||||
WelSegsSet* welsegs_wells{nullptr};
|
||||
std::set<std::string>* compsegs_wells{nullptr};
|
||||
const ScheduleGrid& grid;
|
||||
|
||||
/// \param welsegs_wells All wells with a WELSEGS entry for checks.
|
||||
/// \param compegs_wells All wells with a COMPSEGS entry for checks.
|
||||
HandlerContext(const ScheduleBlock& block_,
|
||||
const DeckKeyword& keyword_,
|
||||
const ScheduleGrid& grid_,
|
||||
const std::size_t currentStep_,
|
||||
const std::vector<std::string>& matching_wells_,
|
||||
bool actionx_mode_,
|
||||
const ParseContext& parseContext_,
|
||||
ErrorGuard& errors_,
|
||||
SimulatorUpdate* sim_update_,
|
||||
const std::unordered_map<std::string, double>* target_wellpi_,
|
||||
std::unordered_map<std::string, double>* wpimult_global_factor_,
|
||||
WelSegsSet* welsegs_wells_,
|
||||
std::set<std::string>* compsegs_wells_)
|
||||
: block(block_)
|
||||
, keyword(keyword_)
|
||||
, currentStep(currentStep_)
|
||||
, matching_wells(matching_wells_)
|
||||
, actionx_mode(actionx_mode_)
|
||||
, parseContext(parseContext_)
|
||||
, errors(errors_)
|
||||
, sim_update(sim_update_)
|
||||
, target_wellpi(target_wellpi_)
|
||||
, wpimult_global_factor(wpimult_global_factor_)
|
||||
, welsegs_wells(welsegs_wells_)
|
||||
, compsegs_wells(compsegs_wells_)
|
||||
, grid(grid_)
|
||||
{}
|
||||
|
||||
void affected_well(const std::string& well_name);
|
||||
void record_well_structure_change();
|
||||
|
||||
/// \brief Mark that the well occured in a WELSEGS keyword
|
||||
void welsegs_handled(const std::string& well_name)
|
||||
{
|
||||
if (welsegs_wells)
|
||||
welsegs_wells->insert({well_name, keyword.location()});
|
||||
}
|
||||
|
||||
/// \brief Mark that the well occured in a COMPSEGS keyword
|
||||
void compsegs_handled(const std::string& well_name)
|
||||
{
|
||||
if (compsegs_wells)
|
||||
compsegs_wells->insert(well_name);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
// Please update the member functions
|
||||
// - operator==(const Schedule&) const
|
||||
// - serializationTestObject()
|
||||
@@ -556,15 +640,13 @@ namespace Opm
|
||||
bool actionx_mode,
|
||||
SimulatorUpdate* sim_update,
|
||||
const std::unordered_map<std::string, double>* target_wellpi,
|
||||
std::unordered_map<std::string, double>& wpimult_global_factor,
|
||||
std::unordered_map<std::string, double>* wpimult_global_factor = nullptr,
|
||||
WelSegsSet* welsegs_wells = nullptr,
|
||||
std::set<std::string>* compsegs_wells = nullptr);
|
||||
|
||||
void prefetch_cell_properties(const ScheduleGrid& grid, const DeckKeyword& keyword);
|
||||
void store_wgnames(const DeckKeyword& keyword);
|
||||
std::vector<std::string> wellNames(const std::string& pattern,
|
||||
const HandlerContext& context,
|
||||
bool allowEmpty = false);
|
||||
std::vector<std::string> wellNames(const std::string& pattern, const HandlerContext& context);
|
||||
std::vector<std::string> wellNames(const std::string& pattern, std::size_t timeStep, const std::vector<std::string>& matching_wells, InputErrorAction error_action, ErrorGuard& errors, const KeywordLocation& location) const;
|
||||
void invalidNamePattern( const std::string& namePattern, const HandlerContext& context) const;
|
||||
static std::string formatDate(std::time_t t);
|
||||
@@ -573,18 +655,6 @@ namespace Opm
|
||||
|
||||
bool must_write_rst_file(std::size_t report_step) const;
|
||||
|
||||
bool isWList(std::size_t report_step, const std::string& pattern) const;
|
||||
|
||||
void welspecsCreateNewWell(const DeckRecord& record,
|
||||
const std::string& wellName,
|
||||
const std::string& groupName,
|
||||
HandlerContext& handlerContext);
|
||||
|
||||
void welspecsUpdateExistingWells(const DeckRecord& record,
|
||||
const std::vector<std::string>& wellNames,
|
||||
const std::string& groupName,
|
||||
HandlerContext& handlerContext);
|
||||
|
||||
void applyEXIT(const DeckKeyword&, std::size_t currentStep);
|
||||
SimulatorUpdate applyAction(std::size_t reportStep, const std::string& action_name, const std::vector<std::string>& matching_wells);
|
||||
|
||||
@@ -628,7 +698,6 @@ namespace Opm
|
||||
void handleDRVDT (HandlerContext&);
|
||||
void handleDRVDTR (HandlerContext&);
|
||||
void handleEXIT (HandlerContext&);
|
||||
void handleFBHPDEF (HandlerContext&);
|
||||
void handleGCONINJE (HandlerContext&);
|
||||
void handleGCONPROD (HandlerContext&);
|
||||
void handleGCONSALE (HandlerContext&);
|
||||
@@ -657,7 +726,6 @@ namespace Opm
|
||||
void handleSAVE (HandlerContext&);
|
||||
void handleSUMTHIN (HandlerContext&);
|
||||
void handleUDQ (HandlerContext&);
|
||||
void handleUDT (HandlerContext&);
|
||||
void handleVAPPARS (HandlerContext&);
|
||||
void handleVFPINJ (HandlerContext&);
|
||||
void handleVFPPROD (HandlerContext&);
|
||||
@@ -666,8 +734,6 @@ namespace Opm
|
||||
void handleWCONINJH (HandlerContext&);
|
||||
void handleWCONPROD (HandlerContext&);
|
||||
void handleWECON (HandlerContext&);
|
||||
void handleWDFACCOR (HandlerContext&);
|
||||
void handleWDFAC (HandlerContext&);
|
||||
void handleWEFAC (HandlerContext&);
|
||||
void handleWELOPEN (HandlerContext&);
|
||||
void handleWELPI (HandlerContext&);
|
||||
|
||||
@@ -20,6 +20,12 @@
|
||||
#ifndef SCHEDULE_TSTEP_HPP
|
||||
#define SCHEDULE_TSTEP_HPP
|
||||
|
||||
#include <chrono>
|
||||
#include <cstddef>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <unordered_map>
|
||||
|
||||
#include <opm/input/eclipse/Deck/DeckKeyword.hpp>
|
||||
#include <opm/common/utility/TimeService.hpp>
|
||||
|
||||
@@ -37,13 +43,6 @@
|
||||
#include <opm/input/eclipse/Schedule/VFPInjTable.hpp>
|
||||
#include <opm/input/eclipse/Schedule/RSTConfig.hpp>
|
||||
|
||||
#include <chrono>
|
||||
#include <cstddef>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <type_traits>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
|
||||
namespace {
|
||||
|
||||
@@ -279,28 +278,7 @@ namespace Opm {
|
||||
std::unordered_map<K, std::shared_ptr<T>> m_data;
|
||||
};
|
||||
|
||||
struct BHPDefaults {
|
||||
std::optional<double> prod_target;
|
||||
std::optional<double> inj_limit;
|
||||
|
||||
static BHPDefaults serializationTestObject()
|
||||
{
|
||||
return BHPDefaults{1.0, 2.0};
|
||||
}
|
||||
|
||||
bool operator==(const BHPDefaults& rhs) const
|
||||
{
|
||||
return this->prod_target == rhs.prod_target
|
||||
&& this->inj_limit == rhs.inj_limit;
|
||||
}
|
||||
|
||||
template<class Serializer>
|
||||
void serializeOp(Serializer& serializer)
|
||||
{
|
||||
serializer(prod_target);
|
||||
serializer(inj_limit);
|
||||
}
|
||||
};
|
||||
|
||||
ScheduleState() = default;
|
||||
explicit ScheduleState(const time_point& start_time);
|
||||
@@ -403,18 +381,10 @@ namespace Opm {
|
||||
ptr_member<RFTConfig> rft_config;
|
||||
ptr_member<RSTConfig> rst_config;
|
||||
|
||||
ptr_member<BHPDefaults> bhp_defaults;
|
||||
template <typename T> struct always_false1 : std::false_type {};
|
||||
|
||||
template <typename T>
|
||||
ptr_member<T>& get() {
|
||||
return const_cast<ptr_member<T>&>(std::as_const(*this).template get<T>());
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
const ptr_member<T>& get() const
|
||||
{
|
||||
struct always_false1 : std::false_type {};
|
||||
|
||||
if constexpr ( std::is_same_v<T, PAvg> )
|
||||
return this->pavg;
|
||||
else if constexpr ( std::is_same_v<T, WellTestConfig> )
|
||||
@@ -451,17 +421,56 @@ namespace Opm {
|
||||
return this->rft_config;
|
||||
else if constexpr ( std::is_same_v<T, RSTConfig> )
|
||||
return this->rst_config;
|
||||
else if constexpr ( std::is_same_v<T, BHPDefaults> )
|
||||
return this->bhp_defaults;
|
||||
else
|
||||
static_assert(always_false1::value, "Template type <T> not supported in get()");
|
||||
static_assert(always_false1<T>::value, "Template type <T> not supported in get()");
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
const ptr_member<T>& get() const {
|
||||
if constexpr ( std::is_same_v<T, PAvg> )
|
||||
return this->pavg;
|
||||
else if constexpr ( std::is_same_v<T, WellTestConfig> )
|
||||
return this->wtest_config;
|
||||
else if constexpr ( std::is_same_v<T, GConSale> )
|
||||
return this->gconsale;
|
||||
else if constexpr ( std::is_same_v<T, GConSump> )
|
||||
return this->gconsump;
|
||||
else if constexpr ( std::is_same_v<T, GroupEconProductionLimits> )
|
||||
return this->gecon;
|
||||
else if constexpr ( std::is_same_v<T, WListManager> )
|
||||
return this->wlist_manager;
|
||||
else if constexpr ( std::is_same_v<T, Network::ExtNetwork> )
|
||||
return this->network;
|
||||
else if constexpr ( std::is_same_v<T, Network::Balance> )
|
||||
return this->network_balance;
|
||||
else if constexpr ( std::is_same_v<T, RPTConfig> )
|
||||
return this->rpt_config;
|
||||
else if constexpr ( std::is_same_v<T, Action::Actions> )
|
||||
return this->actions;
|
||||
else if constexpr ( std::is_same_v<T, UDQActive> )
|
||||
return this->udq_active;
|
||||
else if constexpr ( std::is_same_v<T, NameOrder> )
|
||||
return this->well_order;
|
||||
else if constexpr ( std::is_same_v<T, GroupOrder> )
|
||||
return this->group_order;
|
||||
else if constexpr ( std::is_same_v<T, UDQConfig> )
|
||||
return this->udq;
|
||||
else if constexpr ( std::is_same_v<T, GasLiftOpt> )
|
||||
return this->glo;
|
||||
else if constexpr ( std::is_same_v<T, GuideRateConfig> )
|
||||
return this->guide_rate;
|
||||
else if constexpr ( std::is_same_v<T, RFTConfig> )
|
||||
return this->rft_config;
|
||||
else if constexpr ( std::is_same_v<T, RSTConfig> )
|
||||
return this->rst_config;
|
||||
else
|
||||
static_assert(always_false1<T>::value, "Template type <T> not supported in get()");
|
||||
}
|
||||
|
||||
|
||||
template <typename K, typename T> struct always_false2 : std::false_type {};
|
||||
template <typename K, typename T>
|
||||
map_member<K,T>& get_map()
|
||||
{
|
||||
struct always_false2 : std::false_type {};
|
||||
map_member<K,T>& get_map() {
|
||||
if constexpr ( std::is_same_v<T, VFPProdTable> )
|
||||
return this->vfpprod;
|
||||
else if constexpr ( std::is_same_v<T, VFPInjTable> )
|
||||
@@ -471,7 +480,7 @@ namespace Opm {
|
||||
else if constexpr ( std::is_same_v<T, Well> )
|
||||
return this->wells;
|
||||
else
|
||||
static_assert(always_false2::value, "Template type <K,T> not supported in get_map()");
|
||||
static_assert(always_false2<K,T>::value, "Template type <K,T> not supported in get_map()");
|
||||
}
|
||||
|
||||
map_member<int, VFPProdTable> vfpprod;
|
||||
|
||||
@@ -106,20 +106,6 @@ private:
|
||||
UDQSet eval_number(const UDQVarType target_type,
|
||||
const UDQContext& context) const;
|
||||
|
||||
UDQSet eval_table_lookup(const UDQVarType target_type,
|
||||
const std::string& string_value,
|
||||
const UDQContext& context) const;
|
||||
|
||||
UDQSet eval_table_lookup_field(const std::string& string_value,
|
||||
const UDQContext& context) const;
|
||||
UDQSet eval_table_lookup_group(const std::string& string_value,
|
||||
const UDQContext& context) const;
|
||||
UDQSet eval_table_lookup_segment(const std::string& string_value,
|
||||
const UDQContext& context) const;
|
||||
UDQSet eval_table_lookup_well(const std::string& string_value,
|
||||
const UDQContext& context) const;
|
||||
|
||||
|
||||
void func_tokens(std::set<UDQTokenType>& tokens) const;
|
||||
};
|
||||
|
||||
|
||||
@@ -26,7 +26,6 @@
|
||||
#include <opm/input/eclipse/Schedule/UDQ/UDQFunctionTable.hpp>
|
||||
#include <opm/input/eclipse/Schedule/UDQ/UDQInput.hpp>
|
||||
#include <opm/input/eclipse/Schedule/UDQ/UDQParams.hpp>
|
||||
#include <opm/input/eclipse/Schedule/UDQ/UDT.hpp>
|
||||
|
||||
#include <opm/input/eclipse/EclipseState/Util/OrderedMap.hpp>
|
||||
#include <opm/input/eclipse/EclipseState/Util/IOrderSet.hpp>
|
||||
@@ -102,8 +101,6 @@ namespace Opm {
|
||||
const std::vector<std::string>& expression,
|
||||
std::size_t report_step);
|
||||
|
||||
void add_table(const std::string& name, UDT udt);
|
||||
|
||||
bool clear_pending_assignments();
|
||||
|
||||
void eval_assign(std::size_t report_step,
|
||||
@@ -139,7 +136,6 @@ namespace Opm {
|
||||
std::vector<UDQAssign> assignments(UDQVarType var_type) const;
|
||||
const UDQParams& params() const;
|
||||
const UDQFunctionTable& function_table() const;
|
||||
const std::unordered_map<std::string, UDT>& tables() const;
|
||||
|
||||
bool operator==(const UDQConfig& config) const;
|
||||
void required_summary(std::unordered_set<std::string>& summary_keys) const;
|
||||
@@ -150,7 +146,6 @@ namespace Opm {
|
||||
serializer(udq_params);
|
||||
serializer(m_definitions);
|
||||
serializer(m_assignments);
|
||||
serializer(m_tables);
|
||||
serializer(units);
|
||||
serializer(input_index);
|
||||
serializer(type_count);
|
||||
@@ -176,7 +171,6 @@ namespace Opm {
|
||||
// currently DEFINE'ed or ASSIGN'ed.
|
||||
std::unordered_map<std::string, UDQDefine> m_definitions;
|
||||
std::unordered_map<std::string, UDQAssign> m_assignments;
|
||||
std::unordered_map<std::string, UDT> m_tables;
|
||||
std::unordered_map<std::string, std::string> units;
|
||||
|
||||
IOrderSet<std::string> define_order;
|
||||
|
||||
@@ -36,7 +36,6 @@ namespace Opm {
|
||||
class UDQFunctionTable;
|
||||
class UDQSet;
|
||||
class UDQState;
|
||||
class UDT;
|
||||
class WellMatcher;
|
||||
|
||||
} // namespace Opm
|
||||
@@ -50,7 +49,6 @@ namespace Opm {
|
||||
|
||||
UDQContext(const UDQFunctionTable& udqft,
|
||||
const WellMatcher& wm,
|
||||
const std::unordered_map<std::string, UDT>& tables,
|
||||
SegmentMatcherFactory create_segment_matcher,
|
||||
SummaryState& summary_state,
|
||||
UDQState& udq_state);
|
||||
@@ -59,7 +57,6 @@ namespace Opm {
|
||||
std::optional<double> get_well_var(const std::string& well, const std::string& var) const;
|
||||
std::optional<double> get_group_var(const std::string& group, const std::string& var) const;
|
||||
std::optional<double> get_segment_var(const std::string& well, const std::string& var, std::size_t segment) const;
|
||||
const UDT& get_udt(const std::string& name) const;
|
||||
|
||||
void add(const std::string& key, double value);
|
||||
void update_assign(const std::string& keyword, const UDQSet& udq_result);
|
||||
@@ -76,7 +73,6 @@ namespace Opm {
|
||||
private:
|
||||
const UDQFunctionTable& udqft;
|
||||
const WellMatcher& well_matcher;
|
||||
const std::unordered_map<std::string, UDT>& udt;
|
||||
|
||||
SegmentMatcherFactory create_segment_matcher;
|
||||
mutable std::unique_ptr<SegmentMatcher> segment_matcher;
|
||||
|
||||
@@ -70,7 +70,6 @@ enum class UDQVarType
|
||||
BLOCK_VAR = 7,
|
||||
WELL_VAR = 8,
|
||||
GROUP_VAR = 9,
|
||||
TABLE_LOOKUP = 10,
|
||||
};
|
||||
|
||||
enum class UDQTokenType
|
||||
@@ -125,8 +124,6 @@ enum class UDQTokenType
|
||||
scalar_func_prod = 46,
|
||||
//
|
||||
table_lookup = 47,
|
||||
table_lookup_start = 48,
|
||||
table_lookup_end = 49,
|
||||
//
|
||||
end = 100,
|
||||
};
|
||||
|
||||
@@ -1,66 +0,0 @@
|
||||
/*
|
||||
Copyright 2023 SINTEF Digital
|
||||
|
||||
This file is part of the Open Porous Media project (OPM).
|
||||
|
||||
OPM is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
OPM is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with OPM. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef UDT_HPP
|
||||
#define UDT_HPP
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace Opm {
|
||||
|
||||
class DeckRecord;
|
||||
|
||||
class UDT
|
||||
{
|
||||
public:
|
||||
enum class InterpolationType {
|
||||
NearestNeighbour, //!< Corresponds to 'NV'
|
||||
LinearClamp, //!< Corresponds to 'LC'
|
||||
LinearExtrapolate //!< Corresponds to 'LL'
|
||||
};
|
||||
|
||||
UDT() = default;
|
||||
|
||||
UDT(const std::vector<double>& x_vals,
|
||||
const std::vector<double>& y_vals,
|
||||
InterpolationType interp_type);
|
||||
|
||||
static UDT serializationTestObject();
|
||||
|
||||
double operator()(const double x) const;
|
||||
|
||||
bool operator==(const UDT& data) const;
|
||||
|
||||
template <class Serializer>
|
||||
void serializeOp(Serializer& serializer)
|
||||
{
|
||||
serializer(xvals_);
|
||||
serializer(yvals_);
|
||||
serializer(interp_type_);
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<double> xvals_; //!< Data points
|
||||
std::vector<double> yvals_; //!< Data values
|
||||
InterpolationType interp_type_ = InterpolationType::LinearClamp; //!< Interpolation type
|
||||
};
|
||||
|
||||
} // Namespace Opm
|
||||
|
||||
#endif // UDT_HPP
|
||||
@@ -93,8 +93,6 @@ namespace RestartIO {
|
||||
double re,
|
||||
double connection_length,
|
||||
double skin_factor,
|
||||
double d_factor,
|
||||
double Ke,
|
||||
const int satTableId,
|
||||
const Direction direction,
|
||||
const CTFKind ctf_kind,
|
||||
@@ -125,8 +123,6 @@ namespace RestartIO {
|
||||
double re() const;
|
||||
double connectionLength() const;
|
||||
double skinFactor() const;
|
||||
double dFactor() const;
|
||||
double Ke() const;
|
||||
CTFKind kind() const;
|
||||
const InjMult& injmult() const;
|
||||
bool activeInjMult() const;
|
||||
@@ -140,8 +136,6 @@ namespace RestartIO {
|
||||
void setState(State state);
|
||||
void setComplnum(int compnum);
|
||||
void setSkinFactor(double skin_factor);
|
||||
void setDFactor(double d_factor);
|
||||
void setKe(double Ke);
|
||||
void setCF(double CF);
|
||||
void scaleWellPi(double wellPi);
|
||||
bool prepareWellPIScaling();
|
||||
@@ -151,7 +145,7 @@ namespace RestartIO {
|
||||
void updateSegment(int segment_number_arg,
|
||||
double center_depth_arg,
|
||||
std::size_t compseg_insert_index,
|
||||
const std::optional<std::pair<double,double>>& perf_range);
|
||||
const std::pair<double,double>& perf_range);
|
||||
std::size_t sort_value() const;
|
||||
const bool& getDefaultSatTabId() const;
|
||||
void setDefaultSatTabId(bool id);
|
||||
@@ -180,8 +174,6 @@ namespace RestartIO {
|
||||
serializer(m_re);
|
||||
serializer(m_connection_length);
|
||||
serializer(m_skin_factor);
|
||||
serializer(m_d_factor);
|
||||
serializer(m_Ke);
|
||||
serializer(ijk);
|
||||
serializer(m_global_index);
|
||||
serializer(m_ctfkind);
|
||||
@@ -192,7 +184,6 @@ namespace RestartIO {
|
||||
serializer(segment_number);
|
||||
serializer(m_subject_to_welpi);
|
||||
serializer(m_filter_cake);
|
||||
serializer(m_wpimult);
|
||||
}
|
||||
|
||||
private:
|
||||
@@ -208,8 +199,6 @@ namespace RestartIO {
|
||||
double m_re;
|
||||
double m_connection_length;
|
||||
double m_skin_factor;
|
||||
double m_d_factor;
|
||||
double m_Ke;
|
||||
|
||||
std::array<int,3> ijk;
|
||||
CTFKind m_ctfkind;
|
||||
|
||||
@@ -1,81 +0,0 @@
|
||||
/*
|
||||
Copyright 2023 Equinor.
|
||||
|
||||
This file is part of the Open Porous Media project (OPM).
|
||||
|
||||
OPM is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
OPM is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with OPM. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef WDFAC_HPP_HEADER_INCLUDED
|
||||
#define WDFAC_HPP_HEADER_INCLUDED
|
||||
|
||||
namespace Opm {
|
||||
class DeckRecord;
|
||||
class WellConnections;
|
||||
} // namespace Opm
|
||||
|
||||
namespace Opm { namespace RestartIO {
|
||||
struct RstWell;
|
||||
}} // namespace Opm::RestartIO
|
||||
|
||||
namespace Opm {
|
||||
|
||||
enum class WDFACTYPE {
|
||||
NONE = 1,
|
||||
DFACTOR = 2,
|
||||
DAKEMODEL = 3,
|
||||
CON_DFACTOR = 4
|
||||
};
|
||||
|
||||
class WDFAC
|
||||
{
|
||||
public:
|
||||
static WDFAC serializationTestObject();
|
||||
|
||||
double getDFactor(const Connection& connection, double mu, double rho, double phi) const;
|
||||
void updateWDFAC(const DeckRecord& record);
|
||||
//void updateWDFAC(const RestartIO::RstWell& rst_well);
|
||||
void updateWDFACCOR(const DeckRecord& record);
|
||||
//void updateWDFACOR(const RestartIO::RstWell& rst_well);
|
||||
|
||||
void updateWDFACType(const WellConnections& connections);
|
||||
void updateTotalCF(const WellConnections& connections);
|
||||
bool useDFactor() const;
|
||||
|
||||
bool operator==(const WDFAC& other) const;
|
||||
bool operator!=(const WDFAC& other) const;
|
||||
|
||||
template<class Serializer>
|
||||
void serializeOp(Serializer& serializer)
|
||||
{
|
||||
serializer(m_a);
|
||||
serializer(m_b);
|
||||
serializer(m_c);
|
||||
serializer(m_d);
|
||||
serializer(m_total_cf);
|
||||
serializer(m_type);
|
||||
}
|
||||
|
||||
private:
|
||||
double m_a{0.0};
|
||||
double m_b{0.0};
|
||||
double m_c{0.0};
|
||||
double m_d{0.0};
|
||||
double m_total_cf{-1.0};
|
||||
WDFACTYPE m_type = WDFACTYPE::NONE;
|
||||
};
|
||||
|
||||
} // namespace Opm
|
||||
|
||||
#endif // WDFAC_HPP_HEADER_INCLUDED
|
||||
@@ -68,7 +68,6 @@ class WellSegments;
|
||||
class WellTracerProperties;
|
||||
class WVFPEXP;
|
||||
class WVFPDP;
|
||||
class WDFAC;
|
||||
|
||||
namespace RestartIO {
|
||||
struct RstWell;
|
||||
@@ -168,29 +167,8 @@ public:
|
||||
static WellInjectionProperties serializationTestObject();
|
||||
|
||||
void handleWELTARG(WELTARGCMode cmode, const UDAValue& new_arg, double SIFactorP);
|
||||
|
||||
//! \brief Handle a WCONINJE keyword.
|
||||
//! \param record The deck record to use
|
||||
//! \param bhp_def The default BHP target in input units
|
||||
//! \param availableForGroupControl True if available for group control
|
||||
//! \param well_name Name of well
|
||||
void handleWCONINJE(const DeckRecord& record,
|
||||
const double bhp_def,
|
||||
bool availableForGroupControl,
|
||||
const std::string& well_name);
|
||||
|
||||
//! \brief Handle a WCONINJH keyword.
|
||||
//! \param record The deck record to use
|
||||
//! \param bhp_def The default BHP limit in SI units
|
||||
//! \param is_producer True if well is a producer
|
||||
//! \param well_name Name of well
|
||||
//! \param loc Location of keyword for logging purpuses
|
||||
void handleWCONINJH(const DeckRecord& record,
|
||||
const double bhp_def,
|
||||
const bool is_producer,
|
||||
const std::string& well_name,
|
||||
const KeywordLocation& loc);
|
||||
|
||||
void handleWCONINJE(const DeckRecord& record, bool availableForGroupControl, const std::string& well_name);
|
||||
void handleWCONINJH(const DeckRecord& record, const bool is_producer, const std::string& well_name, const KeywordLocation& loc);
|
||||
bool hasInjectionControl(InjectorCMode controlModeArg) const {
|
||||
if (injectionControls & static_cast<int>(controlModeArg))
|
||||
return true;
|
||||
@@ -261,7 +239,6 @@ public:
|
||||
// BHP and THP limit
|
||||
double bhp_hist_limit = 0.0;
|
||||
double thp_hist_limit = 0.0;
|
||||
bool bhp_hist_limit_defaulted = true; // Tracks whether value was defaulted or not
|
||||
|
||||
// historical BHP and THP under historical mode
|
||||
double BHPH = 0.0;
|
||||
@@ -295,28 +272,8 @@ public:
|
||||
|
||||
// this is used to check whether the specified control mode is an effective history matching production mode
|
||||
static bool effectiveHistoryProductionControl(ProducerCMode cmode);
|
||||
|
||||
//! \brief Handle WCONPROD keyword.
|
||||
//! \param alq_type ALQ type
|
||||
//! \param bhp_def Default BHP target in SI units
|
||||
//! \param unit_system Unit system to use
|
||||
//! \param well Well name
|
||||
//! \param record Deck record to use
|
||||
void handleWCONPROD(const std::optional<VFPProdTable::ALQ_TYPE>& alq_type,
|
||||
const double bhp_def,
|
||||
const UnitSystem& unit_system,
|
||||
const std::string& well,
|
||||
const DeckRecord& record);
|
||||
|
||||
//! \brief Handle WCONHIST keyword.
|
||||
//! \param alq_type ALQ type
|
||||
//! \param bhp_def Default BHP limit in SI units
|
||||
//! \param unit_system Unit system to use
|
||||
//! \param record Deck record to use
|
||||
void handleWCONHIST(const std::optional<VFPProdTable::ALQ_TYPE>& alq_type,
|
||||
const double bhp_def,
|
||||
const UnitSystem& unit_system,
|
||||
const DeckRecord& record);
|
||||
void handleWCONPROD( const std::optional<VFPProdTable::ALQ_TYPE>& alq_type, const UnitSystem& unit_system, const std::string& well, const DeckRecord& record);
|
||||
void handleWCONHIST( const std::optional<VFPProdTable::ALQ_TYPE>& alq_type, const UnitSystem& unit_system, const DeckRecord& record);
|
||||
void handleWELTARG( WELTARGCMode cmode, const UDAValue& new_arg, double SiFactorP);
|
||||
void resetDefaultBHPLimit();
|
||||
void clearControls();
|
||||
@@ -453,8 +410,6 @@ public:
|
||||
const WellTracerProperties& getTracerProperties() const;
|
||||
const WVFPDP& getWVFPDP() const;
|
||||
const WVFPEXP& getWVFPEXP() const;
|
||||
const WDFAC& getWDFAC() const;
|
||||
|
||||
/* The rate of a given phase under the following assumptions:
|
||||
* * Returns zero if production is requested for an injector (and vice
|
||||
* versa)
|
||||
@@ -490,11 +445,11 @@ public:
|
||||
bool updatePrediction(bool prediction_mode);
|
||||
bool updateAutoShutin(bool auto_shutin);
|
||||
bool updateCrossFlow(bool allow_cross_flow);
|
||||
bool updatePVTTable(std::optional<int> pvt_table);
|
||||
bool updateHead(std::optional<int> I, std::optional<int> J);
|
||||
bool updatePVTTable(int pvt_table);
|
||||
bool updateHead(int I, int J);
|
||||
void updateRefDepth();
|
||||
bool updateRefDepth(std::optional<double> ref_dpeth);
|
||||
bool updateDrainageRadius(std::optional<double> drainage_radius);
|
||||
bool updateRefDepth(const std::optional<double>& ref_dpeth);
|
||||
bool updateDrainageRadius(double drainage_radius);
|
||||
void updateSegments(std::shared_ptr<WellSegments> segments_arg);
|
||||
bool updateConnections(std::shared_ptr<WellConnections> connections, bool force);
|
||||
bool updateConnections(std::shared_ptr<WellConnections> connections, const ScheduleGrid& grid);
|
||||
@@ -503,7 +458,6 @@ public:
|
||||
bool updateWellGuideRate(bool available, double guide_rate, GuideRateTarget guide_phase, double scale_factor);
|
||||
bool updateWellGuideRate(double guide_rate);
|
||||
bool updateEfficiencyFactor(double efficiency_factor);
|
||||
|
||||
bool updateSolventFraction(double solvent_fraction);
|
||||
bool updateTracer(std::shared_ptr<WellTracerProperties> tracer_properties);
|
||||
bool updateFoamProperties(std::shared_ptr<WellFoamProperties> foam_properties);
|
||||
@@ -521,8 +475,6 @@ public:
|
||||
void updateWPaveRefDepth(double ref_depth);
|
||||
bool updateWVFPDP(std::shared_ptr<WVFPDP> wvfpdp);
|
||||
bool updateWVFPEXP(std::shared_ptr<WVFPEXP> wvfpexp);
|
||||
bool updateWDFAC(std::shared_ptr<WDFAC> wdfac);
|
||||
|
||||
|
||||
bool handleWELSEGS(const DeckKeyword& keyword);
|
||||
bool handleCOMPSEGS(const DeckKeyword& keyword, const ScheduleGrid& grid, const ParseContext& parseContext, ErrorGuard& errors);
|
||||
@@ -587,7 +539,6 @@ public:
|
||||
serializer(has_produced);
|
||||
serializer(has_injected);
|
||||
serializer(prediction_mode);
|
||||
serializer(derive_refdepth_from_conns_);
|
||||
serializer(econ_limits);
|
||||
serializer(foam_properties);
|
||||
serializer(polymer_properties);
|
||||
@@ -599,7 +550,6 @@ public:
|
||||
serializer(injection);
|
||||
serializer(segments);
|
||||
serializer(wvfpdp);
|
||||
serializer(wdfac);
|
||||
serializer(wvfpexp);
|
||||
serializer(m_pavg);
|
||||
serializer(well_temperature);
|
||||
@@ -636,7 +586,6 @@ private:
|
||||
bool has_produced = false;
|
||||
bool has_injected = false;
|
||||
bool prediction_mode = true;
|
||||
bool derive_refdepth_from_conns_ { true };
|
||||
|
||||
std::shared_ptr<WellEconProductionLimits> econ_limits;
|
||||
std::shared_ptr<WellFoamProperties> foam_properties;
|
||||
@@ -650,8 +599,6 @@ private:
|
||||
std::shared_ptr<WellSegments> segments;
|
||||
std::shared_ptr<WVFPDP> wvfpdp;
|
||||
std::shared_ptr<WVFPEXP> wvfpexp;
|
||||
std::shared_ptr<WDFAC> wdfac;
|
||||
|
||||
Status status;
|
||||
PAvg m_pavg;
|
||||
double well_temperature;
|
||||
|
||||
@@ -78,8 +78,6 @@ namespace Opm {
|
||||
const double re,
|
||||
const double connection_length,
|
||||
const double skin_factor,
|
||||
const double d_factor,
|
||||
const double Ke,
|
||||
const int satTableId,
|
||||
const Connection::Direction direction = Connection::Direction::Z,
|
||||
const Connection::CTFKind ctf_kind = Connection::CTFKind::DeckValue,
|
||||
@@ -185,8 +183,6 @@ namespace Opm {
|
||||
const double re,
|
||||
const double connection_length,
|
||||
const double skin_factor,
|
||||
const double d_factor,
|
||||
const double Ke,
|
||||
const int satTableId,
|
||||
const Connection::Direction direction = Connection::Direction::Z,
|
||||
const Connection::CTFKind ctf_kind = Connection::CTFKind::DeckValue,
|
||||
|
||||
@@ -86,7 +86,6 @@ namespace Opm {
|
||||
moles,
|
||||
ppm,
|
||||
ymodule,
|
||||
dfactor,
|
||||
_count // New entries must be added *before* this
|
||||
};
|
||||
|
||||
@@ -116,8 +115,8 @@ namespace Opm {
|
||||
|
||||
Dimension parse(const std::string& dimension) const;
|
||||
|
||||
double from_si( const std::string& dimension, double ) const;
|
||||
double to_si( const std::string& dimension, double ) const;
|
||||
double from_si( const std::string& dimension, double );
|
||||
double to_si( const std::string& dimension, double );
|
||||
double from_si( measure, double ) const;
|
||||
double to_si( measure, double ) const;
|
||||
void from_si( measure, std::vector<double>& ) const;
|
||||
|
||||
@@ -68,9 +68,6 @@ namespace Opm { namespace RestartIO {
|
||||
/// Whether or not to include lift gas of subordinate wells as
|
||||
/// part of the produced gas entering the network at this node.
|
||||
bool add_lift_gas{false};
|
||||
|
||||
/// Node pressure
|
||||
double pressure{};
|
||||
};
|
||||
|
||||
explicit RstNetwork(std::shared_ptr<EclIO::RestartFileView> rstView,
|
||||
|
||||
@@ -75,9 +75,9 @@ public:
|
||||
{ return 4.60e6; /* [N/m^2] */ }
|
||||
|
||||
/*!
|
||||
* \brief Critical volume of \f$C_1\f$ [m3/kmol].
|
||||
* \brief Critical volume of \f$C_1\f$ [m2/kmol].
|
||||
*/
|
||||
static Scalar criticalVolume() {return 9.863e-2; }
|
||||
static Scalar criticalVolume() {return 9.863e-5; }
|
||||
|
||||
/*!
|
||||
* \brief Acentric factor of \f$C_1\f$.
|
||||
|
||||
@@ -75,9 +75,9 @@ public:
|
||||
{ return 2.10e6; /* [N/m^2] */ }
|
||||
|
||||
/*!
|
||||
* \brief Critical volume of \f$C_10\f$ [m3/kmol].
|
||||
* \brief Critical volume of \f$C_10\f$ [m2/kmol].
|
||||
*/
|
||||
static Scalar criticalVolume() {return 6.098e-1; }
|
||||
static Scalar criticalVolume() {return 6.098e-4; }
|
||||
|
||||
/*!
|
||||
* \brief Acentric factor of \f$C_10\f$.
|
||||
|
||||
@@ -89,7 +89,7 @@ public:
|
||||
* \brief Critical volume of \f$CO_2\f$ [m2/kmol].
|
||||
*/
|
||||
// Critical volume [m3/kmol]
|
||||
static Scalar criticalVolume() {return 9.412e-2; }
|
||||
static Scalar criticalVolume() {return 9.412e-5; }
|
||||
|
||||
/*!
|
||||
* \brief Returns the pressure \f$\mathrm{[Pa]}\f$ at the triple point of \f$CO_2\f$.
|
||||
|
||||
@@ -79,6 +79,7 @@ public:
|
||||
template <class FluidState>
|
||||
static void solve(FluidState& fluid_state,
|
||||
const Dune::FieldVector<typename FluidState::Scalar, numComponents>& z,
|
||||
int spatialIdx,
|
||||
std::string twoPhaseMethod,
|
||||
Scalar tolerance = -1.,
|
||||
int verbosity = 0)
|
||||
@@ -103,6 +104,7 @@ public:
|
||||
// Print header
|
||||
if (verbosity >= 1) {
|
||||
std::cout << "********" << std::endl;
|
||||
std::cout << "Flash calculations on Cell " << spatialIdx << std::endl;
|
||||
std::cout << "Inputs are K = [" << K << "], L = [" << L << "], z = [" << z << "], P = " << fluid_state.pressure(0) << ", and T = " << fluid_state.temperature(0) << std::endl;
|
||||
}
|
||||
|
||||
@@ -243,7 +245,7 @@ protected:
|
||||
|
||||
// Get temperature
|
||||
const auto& T = fluid_state.temperature(0);
|
||||
|
||||
|
||||
// If temperature is below estimated critical temperature --> phase = liquid; else vapor
|
||||
typename Vector::field_type L;
|
||||
if (T < Tc_est) {
|
||||
@@ -258,13 +260,13 @@ protected:
|
||||
else {
|
||||
// Vapor
|
||||
L = 0.0;
|
||||
|
||||
|
||||
// Print
|
||||
if (verbosity >= 1) {
|
||||
std::cout << "Cell is single-phase, vapor (L = 0.0) due to Li's phase labeling method giving T >= Tc_est (" << T << " >= " << Tc_est << ")!" << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
return L;
|
||||
}
|
||||
@@ -344,7 +346,7 @@ protected:
|
||||
|
||||
// Run bisection
|
||||
L = bisection_g_(K, Lmin, Lmax, z, verbosity);
|
||||
|
||||
|
||||
// Ensure that L is in the range (0, 1)
|
||||
L = Opm::min(Opm::max(L, 0.0), 1.0);
|
||||
|
||||
@@ -381,7 +383,7 @@ protected:
|
||||
{
|
||||
// Calculate for g(Lmin) for first comparison with gMid = g(L)
|
||||
typename Vector::field_type gLmin = rachfordRice_g_(K, Lmin, z);
|
||||
|
||||
|
||||
// Print new header
|
||||
if (verbosity >= 3) {
|
||||
std::cout << std::setw(10) << "Iteration" << std::setw(16) << "g(Lmid)" << std::setw(16) << "L" << std::endl;
|
||||
@@ -401,14 +403,14 @@ protected:
|
||||
if (Opm::abs(gMid) < 1e-10 || Opm::abs((Lmax - Lmin) / 2) < 1e-10){
|
||||
return L;
|
||||
}
|
||||
|
||||
|
||||
// Else we repeat with midpoint being either Lmin og Lmax (depending on the signs).
|
||||
else if (Dune::sign(gMid) != Dune::sign(gLmin)) {
|
||||
// gMid has same sign as gLmax, so we set L as the new Lmax
|
||||
Lmax = L;
|
||||
Lmax = L;
|
||||
}
|
||||
else {
|
||||
// gMid and gLmin have same sign so we set L as the new Lmin
|
||||
// gMid and gLmin have same sign so we set L as the new Lmin
|
||||
Lmin = L;
|
||||
gLmin = gMid;
|
||||
}
|
||||
@@ -441,7 +443,7 @@ protected:
|
||||
bool L_stable = (S_l < (1.0 + 1e-5)) || isTrivialL;
|
||||
|
||||
// L-stable means success in making liquid, V-unstable means no success in making vapour
|
||||
isStable = L_stable && V_unstable;
|
||||
isStable = L_stable && V_unstable;
|
||||
if (isStable) {
|
||||
// Single phase, i.e. phase composition is equivalent to the global composition
|
||||
// Update fluid_state with mole fraction
|
||||
@@ -465,7 +467,7 @@ protected:
|
||||
using FlashEval = typename FlashFluidState::Scalar;
|
||||
using PengRobinsonMixture = typename Opm::PengRobinsonMixture<Scalar, FluidSystem>;
|
||||
|
||||
// Declarations
|
||||
// Declarations
|
||||
FlashFluidState fluid_state_fake = fluid_state;
|
||||
FlashFluidState fluid_state_global = fluid_state;
|
||||
|
||||
@@ -521,7 +523,7 @@ protected:
|
||||
fluid_state_global.setFugacityCoefficient(phaseIdx2, compIdx, phiGlobal);
|
||||
}
|
||||
|
||||
|
||||
|
||||
ComponentVector R;
|
||||
for (int compIdx=0; compIdx<numComponents; ++compIdx){
|
||||
if (isGas){
|
||||
@@ -779,7 +781,7 @@ protected:
|
||||
K[idx] = K_i;
|
||||
}
|
||||
L = Opm::getValue(l);
|
||||
fluid_state.setLvalue(L);
|
||||
fluid_state.setLvalue(L);
|
||||
}
|
||||
|
||||
// TODO: the interface will need to refactor for later usage
|
||||
@@ -956,7 +958,7 @@ protected:
|
||||
SecondaryNewtonMatrix sec_jac;
|
||||
SecondaryNewtonVector sec_res;
|
||||
|
||||
|
||||
|
||||
//use the regular equations
|
||||
assembleNewton_<SecondaryFlashFluidState, SecondaryComponentVector, secondary_num_pv, num_equations>
|
||||
(secondary_fluid_state, secondary_z, sec_jac, sec_res);
|
||||
@@ -1024,9 +1026,9 @@ protected:
|
||||
|
||||
// use the chainrule (and using partial instead of total
|
||||
// derivatives, DF / Dp = dF / dp + dF / ds * ds/dp.
|
||||
// where p is the primary variables and s the secondary variables. We then obtain
|
||||
// where p is the primary variables and s the secondary variables. We then obtain
|
||||
// ds / dp = -inv(dF / ds)*(DF / Dp)
|
||||
|
||||
|
||||
const auto p_l = fluid_state.pressure(FluidSystem::oilPhaseIdx);
|
||||
const auto p_v = fluid_state.pressure(FluidSystem::gasPhaseIdx);
|
||||
std::vector<double> K(numComponents);
|
||||
@@ -1036,11 +1038,11 @@ protected:
|
||||
x[compIdx] = fluid_state_scalar.moleFraction(FluidSystem::oilPhaseIdx,compIdx);//;z[compIdx] * 1. / (L + (1 - L) * K[compIdx]);
|
||||
y[compIdx] = fluid_state_scalar.moleFraction(FluidSystem::gasPhaseIdx,compIdx);//;x[compIdx] * K[compIdx];
|
||||
}
|
||||
|
||||
|
||||
// then we try to set the derivatives for x, y and L against P and x.
|
||||
// p_l and p_v are the same here, in the future, there might be slightly more complicated scenarios when capillary
|
||||
// pressure joins
|
||||
|
||||
|
||||
constexpr size_t num_deri = numComponents;
|
||||
for (unsigned compIdx = 0; compIdx < numComponents; ++compIdx) {
|
||||
std::vector<double> deri(num_deri, 0.);
|
||||
@@ -1125,11 +1127,11 @@ protected:
|
||||
const bool newton_afterwards, const int verbosity)
|
||||
{
|
||||
// Determine max. iterations based on if it will be used as a standalone flash or as a pre-process to Newton (or other) method.
|
||||
const int maxIterations = newton_afterwards ? 3 : 100;
|
||||
const int maxIterations = newton_afterwards ? 3 : 10;
|
||||
|
||||
// Store cout format before manipulation
|
||||
std::ios_base::fmtflags f(std::cout.flags());
|
||||
|
||||
|
||||
// Print initial guess
|
||||
if (verbosity >= 1)
|
||||
std::cout << "Initial guess: K = [" << K << "] and L = " << L << std::endl;
|
||||
@@ -1140,9 +1142,9 @@ protected:
|
||||
int convWidth = fugWidth + 7;
|
||||
std::cout << std::setw(10) << "Iteration" << std::setw(fugWidth) << "fL/fV" << std::setw(convWidth) << "norm2(fL/fv-1)" << std::endl;
|
||||
}
|
||||
//
|
||||
//
|
||||
// Successive substitution loop
|
||||
//
|
||||
//
|
||||
for (int i=0; i < maxIterations; ++i){
|
||||
// Compute (normalized) liquid and vapor mole fractions
|
||||
computeLiquidVapor_(fluid_state, L, K, z);
|
||||
@@ -1157,7 +1159,7 @@ protected:
|
||||
fluid_state.setFugacityCoefficient(phaseIdx, compIdx, phi);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Calculate fugacity ratio
|
||||
ComponentVector newFugRatio;
|
||||
ComponentVector convFugRatio;
|
||||
@@ -1167,7 +1169,7 @@ protected:
|
||||
}
|
||||
|
||||
// Print iteration info
|
||||
if (verbosity >= 2) {
|
||||
if (verbosity == 2 || verbosity == 4) {
|
||||
int prec = 5;
|
||||
int fugWidth = (prec + 3);
|
||||
int convWidth = prec + 9;
|
||||
@@ -1182,9 +1184,9 @@ protected:
|
||||
}
|
||||
|
||||
// Check convergence
|
||||
if (convFugRatio.two_norm() < 1e-6) {
|
||||
if (convFugRatio.two_norm() < 1e-6){
|
||||
// Restore cout format
|
||||
std::cout.flags(f);
|
||||
std::cout.flags(f);
|
||||
|
||||
// Print info
|
||||
if (verbosity >= 1) {
|
||||
@@ -1211,7 +1213,7 @@ protected:
|
||||
// Restore cout format format
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// If convergence is not met, K is updated in a successive substitution manner
|
||||
else{
|
||||
// Update K
|
||||
@@ -1226,10 +1228,10 @@ protected:
|
||||
}
|
||||
if (!newton_afterwards) {
|
||||
throw std::runtime_error(
|
||||
"Successive substitution composition update did not converge within maxIterations " + std::to_string(maxIterations));
|
||||
"Successive substitution composition update did not converge within maxIterations");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
};//end PTFlash
|
||||
|
||||
} // namespace Opm
|
||||
|
||||
@@ -54,7 +54,7 @@ namespace Opm {
|
||||
* R. Reid, et al.: The Properties of Gases and Liquids,
|
||||
* 4th edition, McGraw-Hill, 1987, pp. 42-44, 82
|
||||
*/
|
||||
template <class Scalar, bool UseLegacy=true>
|
||||
template <class Scalar>
|
||||
class PengRobinson
|
||||
{
|
||||
//! The ideal gas constant [Pa * m^3/mol/K]
|
||||
@@ -205,25 +205,27 @@ public:
|
||||
Evaluation VmCubic = max(1e-7, Z[0]*RT/p);
|
||||
Vm = VmCubic;
|
||||
|
||||
if (UseLegacy) {
|
||||
// find the extrema (if they are present)
|
||||
Evaluation Vmin, Vmax, pmin, pmax;
|
||||
if (findExtrema_(Vmin, Vmax, pmin, pmax, a, b, T)) {
|
||||
if (isGasPhase)
|
||||
Vm = std::max(Vmax, VmCubic);
|
||||
else {
|
||||
if (Vmin > 0)
|
||||
Vm = std::min(Vmin, VmCubic);
|
||||
else
|
||||
Vm = VmCubic;
|
||||
}
|
||||
} else {
|
||||
// the EOS does not exhibit any physically meaningful
|
||||
// extrema, and the fluid is critical...
|
||||
Vm = VmCubic;
|
||||
handleCriticalFluid_(Vm, fs, params, phaseIdx, isGasPhase);
|
||||
// find the extrema (if they are present)
|
||||
Evaluation Vmin, Vmax, pmin, pmax;
|
||||
if (findExtrema_(Vmin, Vmax,
|
||||
pmin, pmax,
|
||||
a, b, T))
|
||||
{
|
||||
if (isGasPhase)
|
||||
Vm = std::max(Vmax, VmCubic);
|
||||
else {
|
||||
if (Vmin > 0)
|
||||
Vm = std::min(Vmin, VmCubic);
|
||||
else
|
||||
Vm = VmCubic;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// the EOS does not exhibit any physically meaningful
|
||||
// extrema, and the fluid is critical...
|
||||
Vm = VmCubic;
|
||||
handleCriticalFluid_(Vm, fs, params, phaseIdx, isGasPhase);
|
||||
}
|
||||
}
|
||||
|
||||
Valgrind::CheckDefined(Vm);
|
||||
|
||||
@@ -40,6 +40,7 @@ template <class Scalar, class StaticParameters>
|
||||
class PengRobinsonMixture
|
||||
{
|
||||
enum { numComponents = StaticParameters::numComponents };
|
||||
typedef ::Opm::PengRobinson<Scalar> PengRobinson;
|
||||
|
||||
// this class cannot be instantiated!
|
||||
PengRobinsonMixture() = default;
|
||||
@@ -52,6 +53,20 @@ class PengRobinsonMixture
|
||||
static const Scalar w;
|
||||
|
||||
public:
|
||||
/*!
|
||||
* \brief Computes molar volumes where the Peng-Robinson EOS is
|
||||
* true.
|
||||
*
|
||||
* \return Number of solutions.
|
||||
*/
|
||||
template <class MutableParams, class FluidState>
|
||||
static int computeMolarVolumes(Scalar* Vm,
|
||||
const MutableParams& params,
|
||||
unsigned phaseIdx,
|
||||
const FluidState& fs)
|
||||
{
|
||||
return PengRobinson::computeMolarVolumes(Vm, params, phaseIdx, fs);
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Returns the fugacity coefficient of an individual
|
||||
|
||||
@@ -257,16 +257,6 @@ public:
|
||||
Scalar pcow,
|
||||
Scalar Sw);
|
||||
|
||||
/// Apply SWATINIT-like scaling of oil/water capillary pressure curve at
|
||||
/// simulation restart.
|
||||
///
|
||||
/// \param[in] elemIdx Active cell index
|
||||
///
|
||||
/// \param[in] maxPcow Scaled maximum oil/water capillary pressure.
|
||||
/// Typically the PPCW restart file array's entry for the
|
||||
/// corresponding cell.
|
||||
void applyRestartSwatInit(const unsigned elemIdx, const Scalar maxPcow);
|
||||
|
||||
bool enableEndPointScaling() const
|
||||
{ return enableEndPointScaling_; }
|
||||
|
||||
|
||||
@@ -111,7 +111,7 @@ template <class ScalarT,
|
||||
bool enableTemperature = false,
|
||||
bool enableEnergy = false,
|
||||
bool enableDissolution = true,
|
||||
bool enableVapwat = false,
|
||||
bool enableEvaporation = false,
|
||||
bool enableBrine = false,
|
||||
bool enableSaltPrecipitation = false,
|
||||
bool enableDissolutionInWater = false,
|
||||
@@ -159,7 +159,7 @@ public:
|
||||
Valgrind::CheckDefined(*Rv_);
|
||||
}
|
||||
|
||||
if constexpr (enableVapwat) {
|
||||
if constexpr (enableEvaporation) {
|
||||
Valgrind::CheckDefined(*Rvw_);
|
||||
}
|
||||
|
||||
@@ -197,7 +197,7 @@ public:
|
||||
setRs(BlackOil::getRs_<FluidSystem, FluidState, Scalar>(fs, pvtRegionIdx));
|
||||
setRv(BlackOil::getRv_<FluidSystem, FluidState, Scalar>(fs, pvtRegionIdx));
|
||||
}
|
||||
if constexpr (enableVapwat) {
|
||||
if constexpr (enableEvaporation) {
|
||||
setRvw(BlackOil::getRvw_<FluidSystem, FluidState, Scalar>(fs, pvtRegionIdx));
|
||||
}
|
||||
if constexpr (enableDissolutionInWater) {
|
||||
@@ -430,7 +430,7 @@ public:
|
||||
*/
|
||||
const Scalar& Rvw() const
|
||||
{
|
||||
if constexpr (enableVapwat) {
|
||||
if constexpr (enableEvaporation) {
|
||||
return *Rvw_;
|
||||
} else {
|
||||
static Scalar null = 0.0;
|
||||
@@ -685,7 +685,7 @@ private:
|
||||
std::array<Scalar, numStoragePhases> density_;
|
||||
ConditionalStorage<enableDissolution,Scalar> Rs_;
|
||||
ConditionalStorage<enableDissolution, Scalar> Rv_;
|
||||
ConditionalStorage<enableVapwat,Scalar> Rvw_;
|
||||
ConditionalStorage<enableEvaporation,Scalar> Rvw_;
|
||||
ConditionalStorage<enableDissolutionInWater,Scalar> Rsw_;
|
||||
ConditionalStorage<enableBrine, Scalar> saltConcentration_;
|
||||
ConditionalStorage<enableSaltPrecipitation, Scalar> saltSaturation_;
|
||||
|
||||
@@ -34,12 +34,13 @@
|
||||
|
||||
#include <opm/material/fluidsystems/PTFlashParameterCache.hpp>
|
||||
#include <opm/material/viscositymodels/LBC.hpp>
|
||||
//#include <opm/material/viscositymodels/LBC.hpp>
|
||||
|
||||
namespace Opm {
|
||||
/*!
|
||||
* \ingroup FluidSystem
|
||||
*
|
||||
* \brief A two phase two component system with components co2 brine
|
||||
* \brief A two phase two component system, co2 brine
|
||||
*/
|
||||
|
||||
template<class Scalar>
|
||||
@@ -63,6 +64,7 @@ namespace Opm {
|
||||
template <class ValueType>
|
||||
using ParameterCache = Opm::PTFlashParameterCache<ValueType, Co2BrineFluidSystem<Scalar>>;
|
||||
using ViscosityModel = typename Opm::ViscosityModels<Scalar, Co2BrineFluidSystem<Scalar>>;
|
||||
//using ViscosityModel = typename Opm::ViscosityModels<Scalar, Co2BrineFluidSystem<Scalar>>;
|
||||
|
||||
using PengRobinsonMixture = typename Opm::PengRobinsonMixture<Scalar, Co2BrineFluidSystem<Scalar>>;
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@ class PTFlashParameterCache
|
||||
{
|
||||
using ThisType = PTFlashParameterCache<Scalar, FluidSystem>;
|
||||
using ParentType = Opm::ParameterCacheBase<ThisType>;
|
||||
using PengRobinson = Opm::PengRobinson<Scalar, false>; // false refer to discard some old code
|
||||
using PengRobinson = Opm::PengRobinson<Scalar>;
|
||||
|
||||
enum { numPhases = FluidSystem::numPhases };
|
||||
enum { oilPhaseIdx = FluidSystem::oilPhaseIdx };
|
||||
|
||||
@@ -153,7 +153,7 @@ namespace Opm {
|
||||
static const char* name[] = {"o", // oleic phase
|
||||
"g"}; // gas phase
|
||||
|
||||
assert(phaseIdx < 2);
|
||||
assert(0 <= phaseIdx && phaseIdx < 2);
|
||||
return name[phaseIdx];
|
||||
}
|
||||
|
||||
@@ -166,7 +166,7 @@ namespace Opm {
|
||||
Comp2::name(),
|
||||
};
|
||||
|
||||
assert(compIdx < 3);
|
||||
assert(0 <= compIdx && compIdx < 3);
|
||||
return name[compIdx];
|
||||
}
|
||||
|
||||
@@ -196,7 +196,6 @@ namespace Opm {
|
||||
// Use LBC method to calculate viscosity
|
||||
LhsEval mu;
|
||||
mu = ViscosityModel::LBC(fluidState, paramCache, phaseIdx);
|
||||
return mu;
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -49,51 +49,54 @@ public:
|
||||
unsigned phaseIdx)
|
||||
{
|
||||
const Scalar MPa_atm = 0.101325;
|
||||
const Scalar R = Opm::Constants<Scalar>::R;
|
||||
const Scalar R = 8.3144598e-3;//Mj/kmol*K
|
||||
const auto& T = Opm::decay<LhsEval>(fluidState.temperature(phaseIdx));
|
||||
const auto& P = Opm::decay<LhsEval>(fluidState.pressure(phaseIdx));
|
||||
const auto& Z = Opm::decay<LhsEval>(fluidState.compressFactor(phaseIdx));
|
||||
const auto& rho = Opm::decay<LhsEval>(fluidState.density(phaseIdx));
|
||||
|
||||
LhsEval sumMm = 0.0;
|
||||
LhsEval sumVolume = 0.0;
|
||||
for (unsigned compIdx = 0; compIdx < FluidSystem::numComponents; ++compIdx) {
|
||||
const Scalar& p_c = FluidSystem::criticalPressure(compIdx)/1e6; // in Mpa;
|
||||
const Scalar& T_c = FluidSystem::criticalTemperature(compIdx);
|
||||
const Scalar Mm = FluidSystem::molarMass(compIdx) * 1000; //in kg/kmol;
|
||||
const auto& x = Opm::decay<LhsEval>(fluidState.moleFraction(phaseIdx, compIdx));
|
||||
const Scalar v_c = FluidSystem::criticalVolume(compIdx) / 1000; // converting to m3/mol from m3/kmol
|
||||
const Scalar v_c = FluidSystem::criticalVolume(compIdx); // in m3/kmol
|
||||
sumMm += x*Mm;
|
||||
sumVolume += x*v_c;
|
||||
}
|
||||
|
||||
LhsEval rho_pc = 1.0 / sumVolume;
|
||||
LhsEval V = (R * T * Z)/P;
|
||||
LhsEval rho = 1.0 / V;
|
||||
LhsEval rho_r = rho / rho_pc;
|
||||
LhsEval rho_pc = sumMm/sumVolume; //mixture pseudocritical density
|
||||
LhsEval rho_r = rho/rho_pc;
|
||||
|
||||
LhsEval xsum_T_c = 0.0; // mixture pseudocritical temperature
|
||||
LhsEval xsum_Mm = 0.0; // mixture molar mass
|
||||
LhsEval xsum_p_ca = 0.0; // mixture pseudocritical pressure
|
||||
|
||||
LhsEval xsum_T_c = 0.0; //mixture pseudocritical temperature
|
||||
LhsEval xsum_Mm = 0.0; //mixture molar mass
|
||||
LhsEval xsum_p_ca = 0.0; //mixture pseudocritical pressure
|
||||
for (unsigned compIdx = 0; compIdx < FluidSystem::numComponents; ++compIdx) {
|
||||
const Scalar& p_c = FluidSystem::criticalPressure(compIdx) / 1e6; // converting to Mpa from pascal
|
||||
const Scalar& p_c = FluidSystem::criticalPressure(compIdx)/1e6; // in Mpa;
|
||||
const Scalar& T_c = FluidSystem::criticalTemperature(compIdx);
|
||||
const Scalar Mm = FluidSystem::molarMass(compIdx) * 1000; // converting to kg/kmol from kg/mol;
|
||||
const Scalar Mm = FluidSystem::molarMass(compIdx) * 1000; //in kg/kmol;
|
||||
const auto& x = Opm::decay<LhsEval>(fluidState.moleFraction(phaseIdx, compIdx));
|
||||
Scalar p_ca = p_c / MPa_atm;
|
||||
xsum_T_c += x * T_c;
|
||||
xsum_Mm += x * Mm;
|
||||
xsum_p_ca += x * p_ca;
|
||||
xsum_T_c += x*T_c;
|
||||
xsum_Mm += x*Mm;
|
||||
xsum_p_ca += x*p_ca;
|
||||
}
|
||||
LhsEval zeta_tot = Opm::pow(xsum_T_c / (Opm::pow(xsum_Mm,3.0) * Opm::pow(xsum_p_ca,4.0)),1./6);
|
||||
|
||||
LhsEval my0 = 0.0;
|
||||
LhsEval sumxrM = 0.0;
|
||||
for (unsigned compIdx = 0; compIdx < FluidSystem::numComponents; ++compIdx) {
|
||||
const Scalar& p_c = FluidSystem::criticalPressure(compIdx) / 1e6; // converting to Mpa from pa;
|
||||
const Scalar& p_c = FluidSystem::criticalPressure(compIdx)/1e6; // in Mpa;
|
||||
const Scalar& T_c = FluidSystem::criticalTemperature(compIdx);
|
||||
const Scalar Mm = FluidSystem::molarMass(compIdx) * 1000; // converting to kg/kmol from kg/mol;
|
||||
const Scalar Mm = FluidSystem::molarMass(compIdx) * 1000; //in kg/kmol;
|
||||
const auto& x = Opm::decay<LhsEval>(fluidState.moleFraction(phaseIdx, compIdx));
|
||||
Scalar p_ca = p_c / MPa_atm;
|
||||
Scalar zeta = std::pow(T_c / (std::pow(Mm,3.0) * std::pow(p_ca,4.0)),1./6);
|
||||
LhsEval T_r = T/T_c;
|
||||
LhsEval xrM = x * std::pow(Mm,0.5);
|
||||
LhsEval mys = 0.0;
|
||||
if (T_r <= 1.5) {
|
||||
if (T_r <=1.5) {
|
||||
mys = 34.0e-5*Opm::pow(T_r,0.94)/zeta;
|
||||
} else {
|
||||
mys = 17.78e-5*Opm::pow(4.58*T_r - 1.67, 0.625)/zeta;
|
||||
|
||||
@@ -172,42 +172,37 @@ namespace Opm { namespace data {
|
||||
|
||||
struct NodeData {
|
||||
double pressure { 0.0 };
|
||||
double converged_pressure { 0.0 };
|
||||
|
||||
template <class MessageBufferType>
|
||||
void write(MessageBufferType& buffer) const
|
||||
{
|
||||
buffer.write(this->pressure);
|
||||
buffer.write(this->converged_pressure);
|
||||
}
|
||||
|
||||
template <class MessageBufferType>
|
||||
void read(MessageBufferType& buffer)
|
||||
{
|
||||
buffer.read(this->pressure);
|
||||
buffer.read(this->converged_pressure);
|
||||
}
|
||||
|
||||
bool operator==(const NodeData& other) const
|
||||
{
|
||||
return this->pressure == other.pressure && this->converged_pressure == other.converged_pressure;
|
||||
return this->pressure == other.pressure;
|
||||
}
|
||||
|
||||
void init_json(Json::JsonObject& json_data) const {
|
||||
json_data.add_item("pressure", this->pressure);
|
||||
json_data.add_item("converged_pressure", this->converged_pressure);
|
||||
}
|
||||
|
||||
template<class Serializer>
|
||||
void serializeOp(Serializer& serializer)
|
||||
{
|
||||
serializer(pressure);
|
||||
serializer(converged_pressure);
|
||||
}
|
||||
|
||||
static NodeData serializationTestObject()
|
||||
{
|
||||
return NodeData{10.0, 10.0};
|
||||
return NodeData{10.0};
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -24,8 +24,6 @@
|
||||
|
||||
#include <opm/output/data/InterRegFlow.hpp>
|
||||
|
||||
#include <opm/common/utility/CSRGraphFromCoordinates.hpp>
|
||||
|
||||
#include <cstddef>
|
||||
#include <optional>
|
||||
#include <utility>
|
||||
@@ -119,62 +117,415 @@ namespace Opm { namespace data {
|
||||
template <class MessageBufferType>
|
||||
void write(MessageBufferType& buffer) const
|
||||
{
|
||||
this->connections_.write(buffer);
|
||||
this->writeVector(this->rates_, buffer);
|
||||
this->csr_.write(buffer);
|
||||
}
|
||||
|
||||
// MessageBufferType API should be similar to Dune::MessageBufferIF
|
||||
template <class MessageBufferType>
|
||||
void read(MessageBufferType& buffer)
|
||||
{
|
||||
this->connections_.read(buffer);
|
||||
auto other = CSR{};
|
||||
other.read(buffer);
|
||||
|
||||
auto rates = RateBuffer{};
|
||||
this->readVector(buffer, rates);
|
||||
this->appendRates(rates);
|
||||
this->uncompressed_
|
||||
.add(other.maxRowIdx(),
|
||||
other.maxColIdx(),
|
||||
other.coordinateFormatRowIndices(),
|
||||
other.columnIndices(),
|
||||
other.values());
|
||||
}
|
||||
|
||||
/// Clear all internal buffers, but preserve allocated capacity.
|
||||
void clear();
|
||||
|
||||
private:
|
||||
// VertexID = int, TrackCompressedIdx = true.
|
||||
using Graph = utility::CSRGraphFromCoordinates<int, true>;
|
||||
|
||||
Graph connections_{};
|
||||
RateBuffer rates_{};
|
||||
|
||||
template <typename T, class A, class MessageBufferType>
|
||||
void writeVector(const std::vector<T,A>& vec,
|
||||
MessageBufferType& buffer) const
|
||||
/// Coordinate format representation of individual contributions to
|
||||
/// inter-region flows.
|
||||
class Connections
|
||||
{
|
||||
const auto n = vec.size();
|
||||
buffer.write(n);
|
||||
public:
|
||||
/// Add contributions from a single inter-region connection.
|
||||
///
|
||||
/// \param[in] r1 Source region. Zero-based region index/ID.
|
||||
///
|
||||
/// \param[in] r2 Destination region. Zero-based region index.
|
||||
///
|
||||
/// \param[in] rates Flow rates of single inter-region
|
||||
/// connection.
|
||||
void add(const int r1, const int r2, const FlowRates& rates);
|
||||
|
||||
for (const auto& x : vec) {
|
||||
buffer.write(x);
|
||||
/// Add contributions from multiple inter-region connections.
|
||||
///
|
||||
/// \param[in] maxRowIdx Maximum row (source region) index
|
||||
/// across all new inter-region connection contributions.
|
||||
///
|
||||
/// \param[in] maxColIdx Maximum column (destination region)
|
||||
/// index across all new inter-region contributions.
|
||||
///
|
||||
/// \param[in] rows Source region indices for all new
|
||||
/// inter-region connection contributions.
|
||||
///
|
||||
/// \param[in] cols Destination region indices for all new
|
||||
/// inter-region connection contributions.
|
||||
///
|
||||
/// \param[in] rates Flow rate values for all new inter-region
|
||||
/// connection contributions.
|
||||
void add(const int maxRowIdx,
|
||||
const int maxColIdx,
|
||||
const Neighbours& rows,
|
||||
const Neighbours& cols,
|
||||
const RateBuffer& rates);
|
||||
|
||||
/// Clear internal tables. Preserve allocated capacity.
|
||||
void clear();
|
||||
|
||||
/// Predicate.
|
||||
///
|
||||
/// \return Whether or not internal tables are empty.
|
||||
bool empty() const;
|
||||
|
||||
/// Whether or not internal tables meet size consistency
|
||||
/// requirements.
|
||||
bool isValid() const;
|
||||
|
||||
/// Maximum zero-based row (source region) index.
|
||||
int maxRow() const;
|
||||
|
||||
/// Maximum zero-based column (destination region) index.
|
||||
int maxCol() const;
|
||||
|
||||
/// Number of uncompressed contributions in internal tables.
|
||||
Neighbours::size_type numContributions() const;
|
||||
|
||||
/// Read-only access to uncompressed row indices.
|
||||
const Neighbours& rowIndices() const;
|
||||
|
||||
/// Read-only access to uncompressed column indices.
|
||||
const Neighbours& columnIndices() const;
|
||||
|
||||
/// Read-only access to uncompressed flow rate values.
|
||||
const RateBuffer& values() const;
|
||||
|
||||
private:
|
||||
/// Zero-based row/source region indices.
|
||||
Neighbours i_{};
|
||||
|
||||
/// Zero-based column/destination region indices.
|
||||
Neighbours j_{};
|
||||
|
||||
/// Uncompressed flow rate values. Window::bufferSize() entries
|
||||
/// per connection.
|
||||
RateBuffer v_{};
|
||||
|
||||
/// Maximum row index in \code this->i_ \endcode.
|
||||
int max_i_{ -1 };
|
||||
|
||||
/// Maximum column index in \code this->j_ \endcode.
|
||||
int max_j_{ -1 };
|
||||
};
|
||||
|
||||
/// Compressed sparse row representation of inter-region flow rates
|
||||
///
|
||||
/// Row and column indices are zero-based region IDs. Column
|
||||
/// indices ascendingly sorted per row. Value type is window,
|
||||
/// backed by a pair of iterators, of aggregate flow rates per
|
||||
/// region pair.
|
||||
class CSR
|
||||
{
|
||||
public:
|
||||
/// Merge coordinate format into existing CSR map.
|
||||
///
|
||||
/// \param[in] conns Coordinate representation of new
|
||||
/// contributions.
|
||||
///
|
||||
/// \param[in] numRegions Maximum number of regions in this
|
||||
/// region set. Common values/settings are
|
||||
///
|
||||
/// -# Maximum one-based region ID on local MPI rank
|
||||
/// -# Maximum one-based region ID across all MPI ranks
|
||||
/// -# Maximum *possible* one-based region ID in model
|
||||
/// ("NTFIP"), from TABDIMS(5) and/or REGDIMS(1).
|
||||
///
|
||||
/// If this value is smaller than the maximum one-based
|
||||
/// region ID on the local MPI rank, then it will be ignored
|
||||
/// and the local rank's maximum one-based region ID will be
|
||||
/// used instead.
|
||||
void merge(const Connections& conns,
|
||||
const Offset numRegions);
|
||||
|
||||
/// Read-only access to flow rates of given region ID pair.
|
||||
///
|
||||
/// \param[in] i Source region. Zero-based region ID.
|
||||
///
|
||||
/// \param[in] j Destination region. Zero-based region ID.
|
||||
///
|
||||
/// \return Flow rates of region ID pair. Nullopt if no such
|
||||
/// pair exists.
|
||||
std::optional<ReadOnlyWindow> getWindow(const int i, const int j) const;
|
||||
|
||||
/// Total number of rows in compressed map structure.
|
||||
Offset numRows() const;
|
||||
|
||||
/// Maximum zero-based row index encountered mapped structure.
|
||||
int maxRowIdx() const;
|
||||
|
||||
/// Maximum zero-based column index encountered mapped structure.
|
||||
int maxColIdx() const;
|
||||
|
||||
/// Read-only access to compressed structure's start pointers.
|
||||
const Start& startPointers() const;
|
||||
|
||||
/// Read-only access to compressed structure's column indices,
|
||||
/// ascendingly sorted per rwo.
|
||||
const Neighbours& columnIndices() const;
|
||||
|
||||
/// Read-only access to compressed, unique, linearised flow rate
|
||||
/// values. \code Window::bufferSize() \endcode entries per
|
||||
/// non-zero element.
|
||||
const RateBuffer& values() const;
|
||||
|
||||
/// Coordinate format row index vector. Expanded from \code
|
||||
/// startPointers() \endcode.
|
||||
Neighbours coordinateFormatRowIndices() const;
|
||||
|
||||
// MessageBufferType API should be similar to Dune::MessageBufferIF
|
||||
template <class MessageBufferType>
|
||||
void write(MessageBufferType& buffer) const
|
||||
{
|
||||
this->writeVector(this->ia_, buffer);
|
||||
this->writeVector(this->ja_, buffer);
|
||||
this->writeVector(this->sa_, buffer);
|
||||
this->writeVector(this->compressedIdx_, buffer);
|
||||
|
||||
buffer.write(this->numRows_);
|
||||
buffer.write(this->numCols_);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T, class A, class MessageBufferType>
|
||||
void readVector(MessageBufferType& buffer,
|
||||
std::vector<T,A>& vec)
|
||||
{
|
||||
auto n = 0 * vec.size();
|
||||
buffer.read(n);
|
||||
// MessageBufferType API should be similar to Dune::MessageBufferIF
|
||||
template <class MessageBufferType>
|
||||
void read(MessageBufferType& buffer)
|
||||
{
|
||||
this->readVector(buffer, this->ia_);
|
||||
this->readVector(buffer, this->ja_);
|
||||
this->readVector(buffer, this->sa_);
|
||||
this->readVector(buffer, this->compressedIdx_);
|
||||
|
||||
vec.resize(n);
|
||||
|
||||
for (auto& x : vec) {
|
||||
buffer.read(x);
|
||||
buffer.read(this->numRows_);
|
||||
buffer.read(this->numCols_);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Rates>
|
||||
void appendRates(const Rates& rates)
|
||||
{
|
||||
this->rates_.insert(this->rates_.end(), rates.begin(), rates.end());
|
||||
}
|
||||
/// Clear internal tables. Preserve allocated capacity.
|
||||
void clear();
|
||||
|
||||
private:
|
||||
/// Start pointers.
|
||||
Start ia_{};
|
||||
|
||||
/// Column indices. Ascendingly sorted per row once structure
|
||||
/// is fully established.
|
||||
Neighbours ja_{};
|
||||
|
||||
/// Compressed, unique, linearised flow rate values. \code
|
||||
/// Window::bufferSize() \endcode entries per non-zero map
|
||||
/// element.
|
||||
RateBuffer sa_{};
|
||||
|
||||
/// Destination index in compressed representation. Size NNZ.
|
||||
Start compressedIdx_{};
|
||||
|
||||
/// Number of active rows in compressed map structure.
|
||||
int numRows_{ 0 };
|
||||
|
||||
/// Number of active columns in compressed map structure.
|
||||
/// Tracked as the maximum column index plus one.
|
||||
int numCols_{ 0 };
|
||||
|
||||
// ---------------------------------------------------------
|
||||
// Implementation of read()/write()
|
||||
// ---------------------------------------------------------
|
||||
|
||||
template <typename T, class A, class MessageBufferType>
|
||||
void writeVector(const std::vector<T,A>& vec,
|
||||
MessageBufferType& buffer) const
|
||||
{
|
||||
const auto n = vec.size();
|
||||
buffer.write(n);
|
||||
|
||||
for (const auto& x : vec) {
|
||||
buffer.write(x);
|
||||
}
|
||||
}
|
||||
|
||||
template <class MessageBufferType, typename T, class A>
|
||||
void readVector(MessageBufferType& buffer,
|
||||
std::vector<T,A>& vec)
|
||||
{
|
||||
auto n = 0 * vec.size();
|
||||
buffer.read(n);
|
||||
|
||||
vec.resize(n);
|
||||
|
||||
for (auto& x : vec) {
|
||||
buffer.read(x);
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------
|
||||
// Implementation of merge()
|
||||
// ---------------------------------------------------------
|
||||
|
||||
/// Incorporate new, coordinate format contributions into
|
||||
/// existing, possibly empty, CSR mapping structure.
|
||||
///
|
||||
/// On exit the ia_ array holds the proper start pointers while
|
||||
/// ja_ holds the corresponding column indices albeit possibly
|
||||
/// repeated and unsorted.
|
||||
///
|
||||
/// \param[in] rows Row indices of all, possibly repeated,
|
||||
/// coordinate format input contributions. Start pointers \c
|
||||
/// ia_ updated to account for new entries.
|
||||
///
|
||||
/// \param[in] cols Column index of coordinate format intput
|
||||
/// structure. Inserted into \c ja_ according to its
|
||||
/// corresponding row index.
|
||||
///
|
||||
/// \param[in] maxRowIdx Maximum index in \p rows. Needed to
|
||||
/// ensure proper size of \c ia_.
|
||||
///
|
||||
/// \param[in] maxColIdx Maximum index in \p cols.
|
||||
void assemble(const Neighbours& rows,
|
||||
const Neighbours& cols,
|
||||
const int maxRowIdx,
|
||||
const int maxColIdx);
|
||||
|
||||
|
||||
/// Sort column indices per row and compress repeated column
|
||||
/// indices down to a single unique element per row. Sum
|
||||
/// repeated values
|
||||
///
|
||||
/// On exit the \c ia_, \c ja_, and \c sa_ arrays all have their
|
||||
/// expected, canonical structure.
|
||||
///
|
||||
/// \param[in] numRegions Maximum number of regions supported by
|
||||
/// final compressed mapping structure. Ignored if less than
|
||||
/// active number of rows.
|
||||
///
|
||||
/// \param[in] rates Uncompressed flow rate values from
|
||||
/// coordinate format contributions.
|
||||
void compress(const Offset numRegions,
|
||||
const RateBuffer& rates);
|
||||
|
||||
/// Sort column indices within each mapped row.
|
||||
///
|
||||
/// On exit \c ja_ has ascendingly sorted column indices, albeit
|
||||
/// possibly with repeated entries. This function also updates
|
||||
/// \c compressedIdx_ to account for the new locations of the
|
||||
/// non-zero elements in the grouped structure.
|
||||
void sortColumnIndicesPerRow();
|
||||
|
||||
/// Condense repeated column indices per row down to a single
|
||||
/// unique entry for each.
|
||||
///
|
||||
/// Assumes that each row has ascendingly sorted column indices
|
||||
/// in \c ja_ and must therefore be called after member function
|
||||
/// sortColumnIndicesPerRow(). On exit, \c ja_ has its final
|
||||
/// canonical structure and \c compressedIdx_ knows the final
|
||||
/// location of each non-zero contribution in the input
|
||||
/// coordinate format.
|
||||
void condenseDuplicates();
|
||||
|
||||
/// Sum coordinate format flow rates into compressed map
|
||||
/// structure.
|
||||
///
|
||||
/// Repeated (row,column) index pairs in the input coordinate
|
||||
/// format add to the same compressed map element. This
|
||||
/// function assumes that \c compressedIdx_ knows the final
|
||||
/// compressed location of each non-zero contribution in the
|
||||
/// input coordinate format and must therefore be called after
|
||||
/// member function condenseDuplicates(). On exit \c sa_ has
|
||||
/// incorporated all entries from the input coordinate
|
||||
/// structure.
|
||||
///
|
||||
/// \param[in] v Uncompressed flow rate values from coordinate
|
||||
/// format contributions.
|
||||
void accumulateFlowRates(const RateBuffer& v);
|
||||
|
||||
// ---------------------------------------------------------
|
||||
// Implementation of assemble()
|
||||
// ---------------------------------------------------------
|
||||
|
||||
/// Position end pointers at start of row to prepare for column
|
||||
/// index grouping by corresponding row index.
|
||||
///
|
||||
/// Also counts total number of non-zero elements, possibly
|
||||
/// including repetitions, in \code this->ia_[0] \endcode.
|
||||
///
|
||||
/// \param[in] Number of rows in final compressed structure.
|
||||
/// Used to allocate \code this->ia_ \endcode.
|
||||
///
|
||||
/// \param[in] Row indices of all, possibly repeated, coordinate
|
||||
/// format input contributions. Needed to count the number
|
||||
/// of possibly repeated column index entries per row.
|
||||
void preparePushbackRowGrouping(const int numRows,
|
||||
const Neighbours& rowIdx);
|
||||
|
||||
/// Group column indices by corresponding row index and track
|
||||
/// grouped location of original coordinate format element
|
||||
///
|
||||
/// Appends grouped location to \c compressedIdx_.
|
||||
///
|
||||
/// \param[in] rowIdx Row index of coordinate format input
|
||||
/// structure. Used as grouping key.
|
||||
///
|
||||
/// \param[in] colIdx Column index of coordinate format intput
|
||||
/// structure. Inserted into \c ja_ according to its
|
||||
/// corresponding row index.
|
||||
void groupAndTrackColumnIndicesByRow(const Neighbours& rowIdx,
|
||||
const Neighbours& colIdx);
|
||||
|
||||
// ---------------------------------------------------------
|
||||
// General utilities
|
||||
// ---------------------------------------------------------
|
||||
|
||||
/// Transpose connectivity structure.
|
||||
///
|
||||
/// Essentially swaps the roles of rows and columns. Also used
|
||||
/// as a basic building block for sortColumnIndicesPerRow().
|
||||
void transpose();
|
||||
|
||||
/// Condense sequences of repeated column indices in a single
|
||||
/// map row down to a single copy of each unique column index.
|
||||
///
|
||||
/// Appends new unique column indices to \code ja_ \endcode
|
||||
///
|
||||
/// Assumes that the map row has ascendingly sorted column
|
||||
/// indices and therefore has the same requirements as
|
||||
/// std::unique. Will also update the internal compressedIdx_
|
||||
/// mapping to record new compressed locations for the current,
|
||||
/// uncompressed, non-zero map elements.
|
||||
///
|
||||
/// \param[in] begin Start of map row that contains possibly
|
||||
/// repeated column indices.
|
||||
///
|
||||
/// \param[in] end One-past-end of map row that contains
|
||||
/// possibly repeated column indices.
|
||||
void condenseAndTrackUniqueColumnsForSingleRow(Neighbours::const_iterator begin,
|
||||
Neighbours::const_iterator end);
|
||||
|
||||
/// Update \c compressedIdx_ mapping to account for column index
|
||||
/// reshuffling.
|
||||
///
|
||||
/// \param[in] compressedIdx New compressed index locations of
|
||||
/// the non-zero map entries.
|
||||
void remapCompressedIndex(Start&& compressedIdx);
|
||||
};
|
||||
|
||||
/// Accumulated coordinate format contributions that have not yet
|
||||
/// been added to the final CSR structure.
|
||||
Connections uncompressed_;
|
||||
|
||||
/// Canonical representation of unique inter-region flow rates.
|
||||
CSR csr_;
|
||||
};
|
||||
|
||||
}} // namespace Opm::data
|
||||
|
||||
@@ -256,7 +256,6 @@ namespace Opm {
|
||||
double cell_saturation_gas;
|
||||
double effective_Kh;
|
||||
double trans_factor;
|
||||
double d_factor;
|
||||
|
||||
ConnectionFiltrate filtrate;
|
||||
|
||||
@@ -271,7 +270,6 @@ namespace Opm {
|
||||
cell_saturation_gas == conn2.cell_saturation_gas &&
|
||||
effective_Kh == conn2.effective_Kh &&
|
||||
trans_factor == conn2.trans_factor &&
|
||||
d_factor == conn2.d_factor &&
|
||||
filtrate == conn2.filtrate;
|
||||
}
|
||||
|
||||
@@ -294,7 +292,6 @@ namespace Opm {
|
||||
serializer(cell_saturation_gas);
|
||||
serializer(effective_Kh);
|
||||
serializer(trans_factor);
|
||||
serializer(d_factor);
|
||||
serializer(filtrate);
|
||||
}
|
||||
|
||||
@@ -302,7 +299,7 @@ namespace Opm {
|
||||
{
|
||||
return Connection{1, Rates::serializationTestObject(),
|
||||
2.0, 3.0, 4.0, 5.0,
|
||||
6.0, 7.0, 8.0, 9.0,
|
||||
6.0, 7.0, 8.0,
|
||||
ConnectionFiltrate::serializationTestObject() };
|
||||
}
|
||||
};
|
||||
@@ -1186,7 +1183,6 @@ namespace Opm {
|
||||
buffer.write(this->cell_saturation_gas);
|
||||
buffer.write(this->effective_Kh);
|
||||
buffer.write(this->trans_factor);
|
||||
buffer.write(this->d_factor);
|
||||
this->filtrate.write(buffer);
|
||||
}
|
||||
|
||||
@@ -1202,7 +1198,6 @@ namespace Opm {
|
||||
json_data.add_item("sgas", this->cell_saturation_gas);
|
||||
json_data.add_item("Kh", this->effective_Kh);
|
||||
json_data.add_item("trans_factor", this->trans_factor);
|
||||
json_data.add_item("d_factor", this->d_factor);
|
||||
}
|
||||
|
||||
template <class MessageBufferType>
|
||||
@@ -1348,7 +1343,6 @@ namespace Opm {
|
||||
buffer.read(this->cell_saturation_gas);
|
||||
buffer.read(this->effective_Kh);
|
||||
buffer.read(this->trans_factor);
|
||||
buffer.read(this->d_factor);
|
||||
this->filtrate.read(buffer);
|
||||
}
|
||||
|
||||
|
||||
@@ -12,8 +12,6 @@ python_versions[cp37-cp37m]=/opt/python/cp37-cp37m/bin/python
|
||||
python_versions[cp38-cp38]=/opt/python/cp38-cp38/bin/python
|
||||
python_versions[cp39-cp39]=/opt/python/cp39-cp39/bin/python
|
||||
python_versions[cp310-cp310]=/opt/python/cp310-cp310/bin/python
|
||||
python_versions[cp311-cp311]=/opt/python/cp311-cp311/bin/python
|
||||
python_versions[cp312-cp312]=/opt/python/cp312-cp312/bin/python
|
||||
|
||||
source /tmp/opm-common/python/setup-docker-image.sh
|
||||
|
||||
|
||||
70
python/pybind11/.appveyor.yml
Normal file
70
python/pybind11/.appveyor.yml
Normal file
@@ -0,0 +1,70 @@
|
||||
version: 1.0.{build}
|
||||
image:
|
||||
- Visual Studio 2017
|
||||
- Visual Studio 2015
|
||||
test: off
|
||||
skip_branch_with_pr: true
|
||||
build:
|
||||
parallel: true
|
||||
platform:
|
||||
- x64
|
||||
- x86
|
||||
environment:
|
||||
matrix:
|
||||
- PYTHON: 36
|
||||
CPP: 14
|
||||
CONFIG: Debug
|
||||
- PYTHON: 27
|
||||
CPP: 14
|
||||
CONFIG: Debug
|
||||
- CONDA: 36
|
||||
CPP: latest
|
||||
CONFIG: Release
|
||||
matrix:
|
||||
exclude:
|
||||
- image: Visual Studio 2015
|
||||
platform: x86
|
||||
- image: Visual Studio 2015
|
||||
CPP: latest
|
||||
- image: Visual Studio 2017
|
||||
CPP: latest
|
||||
platform: x86
|
||||
install:
|
||||
- ps: |
|
||||
if ($env:PLATFORM -eq "x64") { $env:CMAKE_ARCH = "x64" }
|
||||
if ($env:APPVEYOR_JOB_NAME -like "*Visual Studio 2017*") {
|
||||
$env:CMAKE_GENERATOR = "Visual Studio 15 2017"
|
||||
$env:CMAKE_INCLUDE_PATH = "C:\Libraries\boost_1_64_0"
|
||||
$env:CXXFLAGS = "-permissive-"
|
||||
} else {
|
||||
$env:CMAKE_GENERATOR = "Visual Studio 14 2015"
|
||||
}
|
||||
if ($env:PYTHON) {
|
||||
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
|
||||
} elseif ($env:CONDA) {
|
||||
if ($env:CONDA -eq "27") { $env:CONDA = "" }
|
||||
if ($env:PLATFORM -eq "x64") { $env:CONDA = "$env:CONDA-x64" }
|
||||
$env:PATH = "C:\Miniconda$env:CONDA\;C:\Miniconda$env:CONDA\Scripts\;$env:PATH"
|
||||
$env:PYTHONHOME = "C:\Miniconda$env:CONDA"
|
||||
conda --version
|
||||
conda install -y -q pytest numpy scipy
|
||||
}
|
||||
- ps: |
|
||||
Start-FileDownload 'http://bitbucket.org/eigen/eigen/get/3.3.3.zip'
|
||||
7z x 3.3.3.zip -y > $null
|
||||
$env:CMAKE_INCLUDE_PATH = "eigen-eigen-67e894c6cd8f;$env:CMAKE_INCLUDE_PATH"
|
||||
build_script:
|
||||
- cmake -G "%CMAKE_GENERATOR%" -A "%CMAKE_ARCH%"
|
||||
-DPYBIND11_CPP_STANDARD=/std:c++%CPP%
|
||||
-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%
|
||||
- if "%CPP%"=="latest" (cmake --build . --config %CONFIG% --target test_cmake_build -- /m /v:m /logger:%MSBuildLogger%)
|
||||
on_failure: if exist "tests\test_cmake_build" type tests\test_cmake_build\*.log*
|
||||
38
python/pybind11/.gitignore
vendored
Normal file
38
python/pybind11/.gitignore
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
CMakeCache.txt
|
||||
CMakeFiles
|
||||
Makefile
|
||||
cmake_install.cmake
|
||||
.DS_Store
|
||||
*.so
|
||||
*.pyd
|
||||
*.dll
|
||||
*.sln
|
||||
*.sdf
|
||||
*.opensdf
|
||||
*.vcxproj
|
||||
*.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
|
||||
/cmake/
|
||||
.cache/
|
||||
sosize-*.txt
|
||||
pybind11Config*.cmake
|
||||
pybind11Targets.cmake
|
||||
3
python/pybind11/.gitmodules
vendored
Normal file
3
python/pybind11/.gitmodules
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
[submodule "tools/clang"]
|
||||
path = tools/clang
|
||||
url = ../../wjakob/clang-cindex-python3
|
||||
3
python/pybind11/.readthedocs.yml
Normal file
3
python/pybind11/.readthedocs.yml
Normal file
@@ -0,0 +1,3 @@
|
||||
python:
|
||||
version: 3
|
||||
requirements_file: docs/requirements.txt
|
||||
280
python/pybind11/.travis.yml
Normal file
280
python/pybind11/.travis.yml
Normal file
@@ -0,0 +1,280 @@
|
||||
language: cpp
|
||||
matrix:
|
||||
include:
|
||||
# This config does a few things:
|
||||
# - Checks C++ and Python code styles (check-style.sh and flake8).
|
||||
# - Makes sure sphinx can build the docs without any errors or warnings.
|
||||
# - Tests setup.py sdist and install (all header files should be present).
|
||||
# - Makes sure that everything still works without optional deps (numpy/scipy/eigen) and
|
||||
# also tests the automatic discovery functions in CMake (Python version, C++ standard).
|
||||
- os: linux
|
||||
dist: xenial # Necessary to run doxygen 1.8.15
|
||||
name: Style, docs, and pip
|
||||
cache: false
|
||||
before_install:
|
||||
- pyenv global $(pyenv whence 2to3) # activate all python versions
|
||||
- PY_CMD=python3
|
||||
- $PY_CMD -m pip install --user --upgrade pip wheel setuptools
|
||||
install:
|
||||
- $PY_CMD -m pip install --user --upgrade sphinx sphinx_rtd_theme breathe flake8 pep8-naming pytest
|
||||
- curl -fsSL https://sourceforge.net/projects/doxygen/files/rel-1.8.15/doxygen-1.8.15.linux.bin.tar.gz/download | tar xz
|
||||
- export PATH="$PWD/doxygen-1.8.15/bin:$PATH"
|
||||
script:
|
||||
- tools/check-style.sh
|
||||
- flake8
|
||||
- $PY_CMD -m sphinx -W -b html docs docs/.build
|
||||
- |
|
||||
# Make sure setup.py distributes and installs all the headers
|
||||
$PY_CMD setup.py sdist
|
||||
$PY_CMD -m pip install --user -U ./dist/*
|
||||
installed=$($PY_CMD -c "import pybind11; print(pybind11.get_include(True) + '/pybind11')")
|
||||
diff -rq $installed ./include/pybind11
|
||||
- |
|
||||
# Barebones build
|
||||
cmake -DCMAKE_BUILD_TYPE=Debug -DPYBIND11_WERROR=ON -DDOWNLOAD_CATCH=ON -DPYTHON_EXECUTABLE=$(which $PY_CMD) .
|
||||
make pytest -j 2
|
||||
make cpptest -j 2
|
||||
# The following are regular test configurations, including optional dependencies.
|
||||
# With regard to each other they differ in Python version, C++ standard and compiler.
|
||||
- os: linux
|
||||
dist: trusty
|
||||
name: Python 2.7, c++11, gcc 4.8
|
||||
env: PYTHON=2.7 CPP=11 GCC=4.8
|
||||
addons:
|
||||
apt:
|
||||
packages:
|
||||
- cmake=2.\*
|
||||
- cmake-data=2.\*
|
||||
- os: linux
|
||||
dist: trusty
|
||||
name: Python 3.6, c++11, gcc 4.8
|
||||
env: PYTHON=3.6 CPP=11 GCC=4.8
|
||||
addons:
|
||||
apt:
|
||||
sources:
|
||||
- deadsnakes
|
||||
packages:
|
||||
- python3.6-dev
|
||||
- python3.6-venv
|
||||
- cmake=2.\*
|
||||
- cmake-data=2.\*
|
||||
- os: linux
|
||||
dist: trusty
|
||||
env: PYTHON=2.7 CPP=14 GCC=6 CMAKE=1
|
||||
name: Python 2.7, c++14, gcc 4.8, CMake test
|
||||
addons:
|
||||
apt:
|
||||
sources:
|
||||
- ubuntu-toolchain-r-test
|
||||
packages:
|
||||
- g++-6
|
||||
- os: linux
|
||||
dist: trusty
|
||||
name: Python 3.5, c++14, gcc 6, Debug build
|
||||
# N.B. `ensurepip` could be installed transitively by `python3.5-venv`, but
|
||||
# seems to have apt conflicts (at least for Trusty). Use Docker instead.
|
||||
services: docker
|
||||
env: DOCKER=debian:stretch PYTHON=3.5 CPP=14 GCC=6 DEBUG=1
|
||||
- os: linux
|
||||
dist: xenial
|
||||
env: PYTHON=3.6 CPP=17 GCC=7
|
||||
name: Python 3.6, c++17, gcc 7
|
||||
addons:
|
||||
apt:
|
||||
sources:
|
||||
- deadsnakes
|
||||
- ubuntu-toolchain-r-test
|
||||
packages:
|
||||
- g++-7
|
||||
- python3.6-dev
|
||||
- python3.6-venv
|
||||
- os: linux
|
||||
dist: xenial
|
||||
env: PYTHON=3.6 CPP=17 CLANG=7
|
||||
name: Python 3.6, c++17, Clang 7
|
||||
addons:
|
||||
apt:
|
||||
sources:
|
||||
- deadsnakes
|
||||
- llvm-toolchain-xenial-7
|
||||
packages:
|
||||
- python3.6-dev
|
||||
- python3.6-venv
|
||||
- clang-7
|
||||
- libclang-7-dev
|
||||
- llvm-7-dev
|
||||
- lld-7
|
||||
- libc++-7-dev
|
||||
- libc++abi-7-dev # Why is this necessary???
|
||||
- os: osx
|
||||
name: Python 2.7, c++14, AppleClang 7.3, CMake test
|
||||
osx_image: xcode7.3
|
||||
env: PYTHON=2.7 CPP=14 CLANG CMAKE=1
|
||||
- os: osx
|
||||
name: Python 3.7, c++14, AppleClang 9, Debug build
|
||||
osx_image: xcode9
|
||||
env: PYTHON=3.7 CPP=14 CLANG DEBUG=1
|
||||
# Test a PyPy 2.7 build
|
||||
- os: linux
|
||||
dist: trusty
|
||||
env: PYPY=5.8 PYTHON=2.7 CPP=11 GCC=4.8
|
||||
name: PyPy 5.8, Python 2.7, c++11, gcc 4.8
|
||||
addons:
|
||||
apt:
|
||||
packages:
|
||||
- libblas-dev
|
||||
- liblapack-dev
|
||||
- gfortran
|
||||
# Build in 32-bit mode and tests against the CMake-installed version
|
||||
- os: linux
|
||||
dist: trusty
|
||||
services: docker
|
||||
env: DOCKER=i386/debian:stretch PYTHON=3.5 CPP=14 GCC=6 INSTALL=1
|
||||
name: Python 3.4, c++14, gcc 6, 32-bit
|
||||
script:
|
||||
- |
|
||||
# Consolidated 32-bit Docker Build + Install
|
||||
set -ex
|
||||
$SCRIPT_RUN_PREFIX sh -c "
|
||||
set -ex
|
||||
cmake ${CMAKE_EXTRA_ARGS} -DPYBIND11_INSTALL=1 -DPYBIND11_TEST=0 .
|
||||
make install
|
||||
cp -a tests /pybind11-tests
|
||||
mkdir /build-tests && cd /build-tests
|
||||
cmake ../pybind11-tests ${CMAKE_EXTRA_ARGS} -DPYBIND11_WERROR=ON
|
||||
make pytest -j 2"
|
||||
set +ex
|
||||
cache:
|
||||
directories:
|
||||
- $HOME/.local/bin
|
||||
- $HOME/.local/lib
|
||||
- $HOME/.local/include
|
||||
- $HOME/Library/Python
|
||||
before_install:
|
||||
- |
|
||||
# Configure build variables
|
||||
set -ex
|
||||
if [ "$TRAVIS_OS_NAME" = "linux" ]; then
|
||||
if [ -n "$CLANG" ]; then
|
||||
export CXX=clang++-$CLANG CC=clang-$CLANG
|
||||
EXTRA_PACKAGES+=" clang-$CLANG llvm-$CLANG-dev"
|
||||
else
|
||||
if [ -z "$GCC" ]; then GCC=4.8
|
||||
else EXTRA_PACKAGES+=" g++-$GCC"
|
||||
fi
|
||||
export CXX=g++-$GCC CC=gcc-$GCC
|
||||
fi
|
||||
elif [ "$TRAVIS_OS_NAME" = "osx" ]; then
|
||||
export CXX=clang++ CC=clang;
|
||||
fi
|
||||
if [ -n "$CPP" ]; then CPP=-std=c++$CPP; fi
|
||||
if [ "${PYTHON:0:1}" = "3" ]; then PY=3; fi
|
||||
if [ -n "$DEBUG" ]; then CMAKE_EXTRA_ARGS+=" -DCMAKE_BUILD_TYPE=Debug"; fi
|
||||
set +ex
|
||||
- |
|
||||
# Initialize environment
|
||||
set -ex
|
||||
if [ -n "$DOCKER" ]; then
|
||||
docker pull $DOCKER
|
||||
|
||||
containerid=$(docker run --detach --tty \
|
||||
--volume="$PWD":/pybind11 --workdir=/pybind11 \
|
||||
--env="CC=$CC" --env="CXX=$CXX" --env="DEBIAN_FRONTEND=$DEBIAN_FRONTEND" \
|
||||
--env=GCC_COLORS=\ \
|
||||
$DOCKER)
|
||||
SCRIPT_RUN_PREFIX="docker exec --tty $containerid"
|
||||
$SCRIPT_RUN_PREFIX sh -c 'for s in 0 15; do sleep $s; apt-get update && apt-get -qy dist-upgrade && break; done'
|
||||
else
|
||||
if [ "$PYPY" = "5.8" ]; then
|
||||
curl -fSL https://bitbucket.org/pypy/pypy/downloads/pypy2-v5.8.0-linux64.tar.bz2 | tar xj
|
||||
PY_CMD=$(echo `pwd`/pypy2-v5.8.0-linux64/bin/pypy)
|
||||
CMAKE_EXTRA_ARGS+=" -DPYTHON_EXECUTABLE:FILEPATH=$PY_CMD"
|
||||
else
|
||||
PY_CMD=python$PYTHON
|
||||
if [ "$TRAVIS_OS_NAME" = "osx" ]; then
|
||||
if [ "$PY" = "3" ]; then
|
||||
brew update && brew upgrade python
|
||||
else
|
||||
curl -fsSL https://bootstrap.pypa.io/get-pip.py | $PY_CMD - --user
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
if [ "$PY" = 3 ] || [ -n "$PYPY" ]; then
|
||||
$PY_CMD -m ensurepip --user
|
||||
fi
|
||||
$PY_CMD --version
|
||||
$PY_CMD -m pip install --user --upgrade pip wheel
|
||||
fi
|
||||
set +ex
|
||||
install:
|
||||
- |
|
||||
# Install dependencies
|
||||
set -ex
|
||||
cmake --version
|
||||
if [ -n "$DOCKER" ]; then
|
||||
if [ -n "$DEBUG" ]; then
|
||||
PY_DEBUG="python$PYTHON-dbg python$PY-scipy-dbg"
|
||||
CMAKE_EXTRA_ARGS+=" -DPYTHON_EXECUTABLE=/usr/bin/python${PYTHON}dm"
|
||||
fi
|
||||
$SCRIPT_RUN_PREFIX sh -c "for s in 0 15; do sleep \$s; \
|
||||
apt-get -qy --no-install-recommends install \
|
||||
$PY_DEBUG python$PYTHON-dev python$PY-pytest python$PY-scipy \
|
||||
libeigen3-dev libboost-dev cmake make ${EXTRA_PACKAGES} && break; done"
|
||||
else
|
||||
|
||||
if [ "$CLANG" = "7" ]; then
|
||||
export CXXFLAGS="-stdlib=libc++"
|
||||
fi
|
||||
|
||||
export NPY_NUM_BUILD_JOBS=2
|
||||
echo "Installing pytest, numpy, scipy..."
|
||||
local PIP_CMD=""
|
||||
if [ -n $PYPY ]; then
|
||||
# For expediency, install only versions that are available on the extra index.
|
||||
travis_wait 30 \
|
||||
$PY_CMD -m pip install --user --upgrade --extra-index-url https://imaginary.ca/trusty-pypi \
|
||||
pytest numpy==1.15.4 scipy==1.2.0
|
||||
else
|
||||
$PY_CMD -m pip install --user --upgrade pytest numpy scipy
|
||||
fi
|
||||
echo "done."
|
||||
|
||||
mkdir eigen
|
||||
curl -fsSL https://bitbucket.org/eigen/eigen/get/3.3.4.tar.bz2 | \
|
||||
tar --extract -j --directory=eigen --strip-components=1
|
||||
export CMAKE_INCLUDE_PATH="${CMAKE_INCLUDE_PATH:+$CMAKE_INCLUDE_PATH:}$PWD/eigen"
|
||||
fi
|
||||
set +ex
|
||||
script:
|
||||
- |
|
||||
# CMake Configuration
|
||||
set -ex
|
||||
$SCRIPT_RUN_PREFIX cmake ${CMAKE_EXTRA_ARGS} \
|
||||
-DPYBIND11_PYTHON_VERSION=$PYTHON \
|
||||
-DPYBIND11_CPP_STANDARD=$CPP \
|
||||
-DPYBIND11_WERROR=${WERROR:-ON} \
|
||||
-DDOWNLOAD_CATCH=${DOWNLOAD_CATCH:-ON} \
|
||||
.
|
||||
set +ex
|
||||
- |
|
||||
# pytest
|
||||
set -ex
|
||||
$SCRIPT_RUN_PREFIX make pytest -j 2 VERBOSE=1
|
||||
set +ex
|
||||
- |
|
||||
# cpptest
|
||||
set -ex
|
||||
$SCRIPT_RUN_PREFIX make cpptest -j 2
|
||||
set +ex
|
||||
- |
|
||||
# CMake Build Interface
|
||||
set -ex
|
||||
if [ -n "$CMAKE" ]; then $SCRIPT_RUN_PREFIX make test_cmake_build; fi
|
||||
set +ex
|
||||
after_failure: cat tests/test_cmake_build/*.log*
|
||||
after_script:
|
||||
- |
|
||||
# Cleanup (Docker)
|
||||
set -ex
|
||||
if [ -n "$DOCKER" ]; then docker stop "$containerid"; docker rm "$containerid"; fi
|
||||
set +ex
|
||||
157
python/pybind11/CMakeLists.txt
Normal file
157
python/pybind11/CMakeLists.txt
Normal file
@@ -0,0 +1,157 @@
|
||||
# CMakeLists.txt -- Build system for the pybind11 modules
|
||||
#
|
||||
# Copyright (c) 2015 Wenzel Jakob <wenzel@inf.ethz.ch>
|
||||
#
|
||||
# All rights reserved. Use of this source code is governed by a
|
||||
# BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
cmake_minimum_required(VERSION 2.8.12)
|
||||
|
||||
if (POLICY CMP0048)
|
||||
# cmake warns if loaded from a min-3.0-required parent dir, so silence the warning:
|
||||
cmake_policy(SET CMP0048 NEW)
|
||||
endif()
|
||||
|
||||
# CMake versions < 3.4.0 do not support try_compile/pthread checks without C as active language.
|
||||
if(CMAKE_VERSION VERSION_LESS 3.4.0)
|
||||
project(pybind11)
|
||||
else()
|
||||
project(pybind11 CXX)
|
||||
endif()
|
||||
|
||||
# Check if pybind11 is being used directly or via add_subdirectory
|
||||
set(PYBIND11_MASTER_PROJECT OFF)
|
||||
if (CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR)
|
||||
set(PYBIND11_MASTER_PROJECT ON)
|
||||
endif()
|
||||
|
||||
option(PYBIND11_INSTALL "Install pybind11 header files?" ${PYBIND11_MASTER_PROJECT})
|
||||
option(PYBIND11_TEST "Build pybind11 test suite?" ${PYBIND11_MASTER_PROJECT})
|
||||
|
||||
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/tools")
|
||||
|
||||
include(pybind11Tools)
|
||||
|
||||
# Cache variables so pybind11_add_module can be used in parent projects
|
||||
set(PYBIND11_INCLUDE_DIR "${CMAKE_CURRENT_LIST_DIR}/include" CACHE INTERNAL "")
|
||||
set(PYTHON_INCLUDE_DIRS ${PYTHON_INCLUDE_DIRS} CACHE INTERNAL "")
|
||||
set(PYTHON_LIBRARIES ${PYTHON_LIBRARIES} CACHE INTERNAL "")
|
||||
set(PYTHON_MODULE_PREFIX ${PYTHON_MODULE_PREFIX} CACHE INTERNAL "")
|
||||
set(PYTHON_MODULE_EXTENSION ${PYTHON_MODULE_EXTENSION} CACHE INTERNAL "")
|
||||
set(PYTHON_VERSION_MAJOR ${PYTHON_VERSION_MAJOR} CACHE INTERNAL "")
|
||||
set(PYTHON_VERSION_MINOR ${PYTHON_VERSION_MINOR} CACHE INTERNAL "")
|
||||
|
||||
# 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/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/embed.h
|
||||
include/pybind11/eval.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
|
||||
)
|
||||
string(REPLACE "include/" "${CMAKE_CURRENT_SOURCE_DIR}/include/"
|
||||
PYBIND11_HEADERS "${PYBIND11_HEADERS}")
|
||||
|
||||
if (PYBIND11_TEST)
|
||||
add_subdirectory(tests)
|
||||
endif()
|
||||
|
||||
include(GNUInstallDirs)
|
||||
include(CMakePackageConfigHelpers)
|
||||
|
||||
# extract project version from source
|
||||
file(STRINGS "${PYBIND11_INCLUDE_DIR}/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}" CACHE INTERNAL "")
|
||||
endif()
|
||||
endforeach()
|
||||
set(${PROJECT_NAME}_VERSION ${PYBIND11_VERSION_MAJOR}.${PYBIND11_VERSION_MINOR}.${PYBIND11_VERSION_PATCH})
|
||||
message(STATUS "pybind11 v${${PROJECT_NAME}_VERSION}")
|
||||
|
||||
option (USE_PYTHON_INCLUDE_DIR "Install pybind11 headers in Python include directory instead of default installation prefix" OFF)
|
||||
if (USE_PYTHON_INCLUDE_DIR)
|
||||
file(RELATIVE_PATH CMAKE_INSTALL_INCLUDEDIR ${CMAKE_INSTALL_PREFIX} ${PYTHON_INCLUDE_DIRS})
|
||||
endif()
|
||||
|
||||
if(NOT (CMAKE_VERSION VERSION_LESS 3.0)) # CMake >= 3.0
|
||||
# Build an interface library target:
|
||||
add_library(pybind11 INTERFACE)
|
||||
add_library(pybind11::pybind11 ALIAS pybind11) # to match exported target
|
||||
target_include_directories(pybind11 INTERFACE $<BUILD_INTERFACE:${PYBIND11_INCLUDE_DIR}>
|
||||
$<BUILD_INTERFACE:${PYTHON_INCLUDE_DIRS}>
|
||||
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>)
|
||||
target_compile_options(pybind11 INTERFACE $<BUILD_INTERFACE:${PYBIND11_CPP_STANDARD}>)
|
||||
|
||||
add_library(module INTERFACE)
|
||||
add_library(pybind11::module ALIAS module)
|
||||
if(NOT MSVC)
|
||||
target_compile_options(module INTERFACE -fvisibility=hidden)
|
||||
endif()
|
||||
target_link_libraries(module INTERFACE pybind11::pybind11)
|
||||
if(WIN32 OR CYGWIN)
|
||||
target_link_libraries(module INTERFACE $<BUILD_INTERFACE:${PYTHON_LIBRARIES}>)
|
||||
elseif(APPLE)
|
||||
target_link_libraries(module INTERFACE "-undefined dynamic_lookup")
|
||||
endif()
|
||||
|
||||
add_library(embed INTERFACE)
|
||||
add_library(pybind11::embed ALIAS embed)
|
||||
target_link_libraries(embed INTERFACE pybind11::pybind11 $<BUILD_INTERFACE:${PYTHON_LIBRARIES}>)
|
||||
endif()
|
||||
|
||||
if (PYBIND11_INSTALL)
|
||||
install(DIRECTORY ${PYBIND11_INCLUDE_DIR}/pybind11 DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
|
||||
# GNUInstallDirs "DATADIR" wrong here; CMake search path wants "share".
|
||||
set(PYBIND11_CMAKECONFIG_INSTALL_DIR "share/cmake/${PROJECT_NAME}" CACHE STRING "install path for pybind11Config.cmake")
|
||||
|
||||
configure_package_config_file(tools/${PROJECT_NAME}Config.cmake.in
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Config.cmake"
|
||||
INSTALL_DESTINATION ${PYBIND11_CMAKECONFIG_INSTALL_DIR})
|
||||
# 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_NAME}_VERSION}
|
||||
COMPATIBILITY AnyNewerVersion)
|
||||
set(CMAKE_SIZEOF_VOID_P ${_PYBIND11_CMAKE_SIZEOF_VOID_P})
|
||||
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Config.cmake
|
||||
${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake
|
||||
tools/FindPythonLibsNew.cmake
|
||||
tools/pybind11Tools.cmake
|
||||
DESTINATION ${PYBIND11_CMAKECONFIG_INSTALL_DIR})
|
||||
|
||||
if(NOT (CMAKE_VERSION VERSION_LESS 3.0))
|
||||
if(NOT PYBIND11_EXPORT_NAME)
|
||||
set(PYBIND11_EXPORT_NAME "${PROJECT_NAME}Targets")
|
||||
endif()
|
||||
|
||||
install(TARGETS pybind11 module embed
|
||||
EXPORT "${PYBIND11_EXPORT_NAME}")
|
||||
if(PYBIND11_MASTER_PROJECT)
|
||||
install(EXPORT "${PYBIND11_EXPORT_NAME}"
|
||||
NAMESPACE "${PROJECT_NAME}::"
|
||||
DESTINATION ${PYBIND11_CMAKECONFIG_INSTALL_DIR})
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
49
python/pybind11/CONTRIBUTING.md
Normal file
49
python/pybind11/CONTRIBUTING.md
Normal file
@@ -0,0 +1,49 @@
|
||||
Thank you for your interest in this project! Please refer to the following
|
||||
sections on how to contribute code and bug reports.
|
||||
|
||||
### Reporting bugs
|
||||
|
||||
At the moment, this project is run in the spare time of a single person
|
||||
([Wenzel Jakob](http://rgl.epfl.ch/people/wjakob)) with very limited resources
|
||||
for issue tracker tickets. Thus, 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
|
||||
[http://pybind11.readthedocs.org/en/latest](http://pybind11.readthedocs.org/en/latest).
|
||||
|
||||
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 on my end.
|
||||
|
||||
## Pull requests
|
||||
Contributions are submitted, reviewed, and accepted using Github pull requests.
|
||||
Please refer to [this
|
||||
article](https://help.github.com/articles/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 (``make pytest``)
|
||||
to ensure that no existing features break.
|
||||
* Please run ``flake8`` and ``tools/check-style.sh`` to check your code matches
|
||||
the project style. (Note that ``check-style.sh`` requires ``gawk``.)
|
||||
* 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.
|
||||
17
python/pybind11/ISSUE_TEMPLATE.md
Normal file
17
python/pybind11/ISSUE_TEMPLATE.md
Normal file
@@ -0,0 +1,17 @@
|
||||
Make sure you've completed the following steps before submitting your issue -- thank you!
|
||||
|
||||
1. Check if your question has already been answered in the [FAQ](http://pybind11.readthedocs.io/en/latest/faq.html) section.
|
||||
2. Make sure you've read the [documentation](http://pybind11.readthedocs.io/en/latest/). Your issue may be addressed there.
|
||||
3. If those resources didn't help and you only have a short question (not a bug report), consider asking in the [Gitter chat room](https://gitter.im/pybind/Lobby).
|
||||
4. If you have a genuine bug report or a more complex question which is not answered in the previous items (or not suitable for chat), please fill in the details below.
|
||||
5. Include a self-contained and minimal piece of code that reproduces the problem. If that's not possible, try to make the description as clear as possible.
|
||||
|
||||
*After reading, remove this checklist and the template text in parentheses below.*
|
||||
|
||||
## Issue description
|
||||
|
||||
(Provide a short description, state the expected behavior and what actually happens.)
|
||||
|
||||
## Reproducible example code
|
||||
|
||||
(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.)
|
||||
29
python/pybind11/LICENSE
Normal file
29
python/pybind11/LICENSE
Normal file
@@ -0,0 +1,29 @@
|
||||
Copyright (c) 2016 Wenzel Jakob <wenzel.jakob@epfl.ch>, 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 CONTRIBUTING.md, which clarifies licensing of
|
||||
external contributions to this project including patches, pull requests, etc.
|
||||
2
python/pybind11/MANIFEST.in
Normal file
2
python/pybind11/MANIFEST.in
Normal file
@@ -0,0 +1,2 @@
|
||||
recursive-include include/pybind11 *.h
|
||||
include LICENSE README.md CONTRIBUTING.md
|
||||
132
python/pybind11/README.md
Normal file
132
python/pybind11/README.md
Normal file
@@ -0,0 +1,132 @@
|
||||
Note:
|
||||
This is a dump of the pybind11 distribution, version 2.3.0.
|
||||
A more elegant solutions might be explored in the future.
|
||||
|
||||
|
||||

|
||||
|
||||
# pybind11 — Seamless operability between C++11 and Python
|
||||
|
||||
[](http://pybind11.readthedocs.org/en/master/?badge=master)
|
||||
[](http://pybind11.readthedocs.org/en/stable/?badge=stable)
|
||||
[](https://gitter.im/pybind/Lobby)
|
||||
[](https://travis-ci.org/pybind/pybind11)
|
||||
[](https://ci.appveyor.com/project/wjakob/pybind11)
|
||||
|
||||
**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](http://www.boost.org/doc/libs/1_58_0/libs/python/doc/) 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 (2.7 or 3.x, or PyPy2.7 >= 5.7) and the C++ standard library. This
|
||||
compact implementation was possible thanks to some of the new 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
|
||||
[http://pybind11.readthedocs.org/en/master](http://pybind11.readthedocs.org/en/master).
|
||||
A PDF version of the manual is available
|
||||
[here](https://media.readthedocs.org/pdf/pybind11/master/pybind11.pdf).
|
||||
|
||||
## 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
|
||||
|
||||
## Goodies
|
||||
In addition to the core functionality, pybind11 provides some extra goodies:
|
||||
|
||||
- Python 2.7, 3.x, and PyPy (PyPy2.7 >= 5.7) are supported with an
|
||||
implementation-agnostic interface.
|
||||
|
||||
- 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](http://graylab.jhu.edu/RosettaCon2016/PyRosetta-4.pdf) 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 2015 Update 3 or newer
|
||||
4. Intel C++ compiler 17 or newer (16 with pybind11 v2.0 and 15 with pybind11 v2.0 and a [workaround](https://github.com/pybind/pybind11/issues/276))
|
||||
5. Cygwin/GCC (tested on 2.5.1)
|
||||
|
||||
## About
|
||||
|
||||
This project was created by [Wenzel Jakob](http://rgl.epfl.ch/people/wjakob).
|
||||
Significant features and/or improvements to the code were contributed by
|
||||
Jonas Adler,
|
||||
Sylvain Corlay,
|
||||
Trent Houliston,
|
||||
Axel Huebl,
|
||||
@hulucc,
|
||||
Sergey Lyskov
|
||||
Johan Mabille,
|
||||
Tomasz Miąsko,
|
||||
Dean Moldovan,
|
||||
Ben Pritchard,
|
||||
Jason Rhinelander,
|
||||
Boris Schäling,
|
||||
Pim Schellart,
|
||||
Ivan Smirnov, and
|
||||
Patrick Stewart.
|
||||
|
||||
### 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.
|
||||
11
python/pybind11/docs/_static/theme_overrides.css
vendored
Normal file
11
python/pybind11/docs/_static/theme_overrides.css
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
.wy-table-responsive table td,
|
||||
.wy-table-responsive table th {
|
||||
white-space: initial !important;
|
||||
}
|
||||
.rst-content table.docutils td {
|
||||
vertical-align: top !important;
|
||||
}
|
||||
div[class^='highlight'] pre {
|
||||
white-space: pre;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
81
python/pybind11/docs/advanced/cast/chrono.rst
Normal file
81
python/pybind11/docs/advanced/cast/chrono.rst
Normal file
@@ -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`` → ``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.
|
||||
91
python/pybind11/docs/advanced/cast/custom.rst
Normal file
91
python/pybind11/docs/advanced/cast/custom.rst
Normal file
@@ -0,0 +1,91 @@
|
||||
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
|
||||
a partial overload to the ``pybind11::detail::type_caster<T>`` template.
|
||||
Although this is an implementation detail, adding partial overloads to this
|
||||
type is explicitly allowed.
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
namespace pybind11 { namespace detail {
|
||||
template <> struct type_caster<inty> {
|
||||
public:
|
||||
/**
|
||||
* This macro establishes the name 'inty' in
|
||||
* function signatures and declares a local variable
|
||||
* 'value' of type inty
|
||||
*/
|
||||
PYBIND11_TYPE_CASTER(inty, _("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::detail
|
||||
|
||||
.. note::
|
||||
|
||||
A ``type_caster<T>`` 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.
|
||||
310
python/pybind11/docs/advanced/cast/eigen.rst
Normal file
310
python/pybind11/docs/advanced/cast/eigen.rst
Normal file
@@ -0,0 +1,310 @@
|
||||
Eigen
|
||||
#####
|
||||
|
||||
`Eigen <http://eigen.tuxfamily.org>`_ 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<MatrixType>`` 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<const MatrixType>``
|
||||
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<MatrixType>`` (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 can write code such as the following and have it work as
|
||||
expected:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
void scale_by_2(Eigen::Ref<Eigen::VectorXd> 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_<MyClass>(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<MatrixType>`` 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<MatrixType>`` to the
|
||||
more general ``Eigen::Ref<MatrixType, 0, Eigen::Stride<Eigen::Dynamic,
|
||||
Eigen::Dynamic>>`` (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<MatrixType>`` 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<Eigen::MatrixXd> 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<double, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor>;
|
||||
// Use RowMatrixXd instead of MatrixXd
|
||||
|
||||
Now bound functions accepting ``Eigen::Ref<RowMatrixXd>`` 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<MatrixXd>`` (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<const MatrixType>`` 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<const MatrixXd> &matrix) { /* ... */ }
|
||||
};
|
||||
float some_function(const Eigen::Ref<const MatrixXf> &big,
|
||||
const Eigen::Ref<const MatrixXf> &small) {
|
||||
// ...
|
||||
}
|
||||
|
||||
// The associated binding code:
|
||||
using namespace pybind11::literals; // for "arg"_a
|
||||
py::class_<MyClass>(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<MatrixXd>`` 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<double, Dynamic, 5>``: 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<float, Dynamic, 4>``), 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.
|
||||
109
python/pybind11/docs/advanced/cast/functional.rst
Normal file
109
python/pybind11/docs/advanced/cast/functional.rst
Normal file
@@ -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<int(int)> &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<int(int)> func_ret(const std::function<int(int)> &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/functional.h>
|
||||
|
||||
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.
|
||||
42
python/pybind11/docs/advanced/cast/index.rst
Normal file
42
python/pybind11/docs/advanced/cast/index.rst
Normal file
@@ -0,0 +1,42 @@
|
||||
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
|
||||
|
||||
165
python/pybind11/docs/advanced/cast/overview.rst
Normal file
165
python/pybind11/docs/advanced/cast/overview.rst
Normal file
@@ -0,0 +1,165 @@
|
||||
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<int> &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<int>`` 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<T1, T2>`` | 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<T>`` | Complex numbers | :file:`pybind11/complex.h` |
|
||||
+------------------------------------+---------------------------+-------------------------------+
|
||||
| ``std::array<T, Size>`` | STL static array | :file:`pybind11/stl.h` |
|
||||
+------------------------------------+---------------------------+-------------------------------+
|
||||
| ``std::vector<T>`` | STL dynamic array | :file:`pybind11/stl.h` |
|
||||
+------------------------------------+---------------------------+-------------------------------+
|
||||
| ``std::deque<T>`` | STL double-ended queue | :file:`pybind11/stl.h` |
|
||||
+------------------------------------+---------------------------+-------------------------------+
|
||||
| ``std::valarray<T>`` | STL value array | :file:`pybind11/stl.h` |
|
||||
+------------------------------------+---------------------------+-------------------------------+
|
||||
| ``std::list<T>`` | STL linked list | :file:`pybind11/stl.h` |
|
||||
+------------------------------------+---------------------------+-------------------------------+
|
||||
| ``std::map<T1, T2>`` | STL ordered map | :file:`pybind11/stl.h` |
|
||||
+------------------------------------+---------------------------+-------------------------------+
|
||||
| ``std::unordered_map<T1, T2>`` | STL unordered map | :file:`pybind11/stl.h` |
|
||||
+------------------------------------+---------------------------+-------------------------------+
|
||||
| ``std::set<T>`` | STL ordered set | :file:`pybind11/stl.h` |
|
||||
+------------------------------------+---------------------------+-------------------------------+
|
||||
| ``std::unordered_set<T>`` | STL unordered set | :file:`pybind11/stl.h` |
|
||||
+------------------------------------+---------------------------+-------------------------------+
|
||||
| ``std::optional<T>`` | STL optional type (C++17) | :file:`pybind11/stl.h` |
|
||||
+------------------------------------+---------------------------+-------------------------------+
|
||||
| ``std::experimental::optional<T>`` | STL optional type (exp.) | :file:`pybind11/stl.h` |
|
||||
+------------------------------------+---------------------------+-------------------------------+
|
||||
| ``std::variant<...>`` | Type-safe union (C++17) | :file:`pybind11/stl.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` |
|
||||
+------------------------------------+---------------------------+-------------------------------+
|
||||
240
python/pybind11/docs/advanced/cast/stl.rst
Normal file
240
python/pybind11/docs/advanced/cast/stl.rst
Normal file
@@ -0,0 +1,240 @@
|
||||
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::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 detail {
|
||||
template <typename T>
|
||||
struct type_caster<boost::optional<T>> : optional_caster<boost::optional<T>> {};
|
||||
}}
|
||||
|
||||
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 detail {
|
||||
template <typename... Ts>
|
||||
struct type_caster<boost::variant<Ts...>> : variant_caster<boost::variant<Ts...>> {};
|
||||
|
||||
// Specifies the function used to visit the variant -- `apply_visitor` instead of `visit`
|
||||
template <>
|
||||
struct visit_helper<boost::variant> {
|
||||
template <typename... Args>
|
||||
static auto call(Args &&...args) -> decltype(boost::apply_visitor(args...)) {
|
||||
return boost::apply_visitor(args...);
|
||||
}
|
||||
};
|
||||
}} // namespace pybind11::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.
|
||||
|
||||
.. note::
|
||||
|
||||
pybind11 only supports the modern implementation of ``boost::variant``
|
||||
which makes use of variadic templates. This requires Boost 1.56 or newer.
|
||||
Additionally, on Windows, MSVC 2017 is required because ``boost::variant``
|
||||
falls back to the old non-variadic implementation on MSVC 2015.
|
||||
|
||||
.. _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<int> &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<int> contents;
|
||||
};
|
||||
|
||||
/* ... binding code ... */
|
||||
|
||||
py::class_<MyClass>(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<int>`` into an opaque type, add
|
||||
the declaration
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
PYBIND11_MAKE_OPAQUE(std::vector<int>);
|
||||
|
||||
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 instantiates a partial template overload. 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<int>``. 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_<std::vector<int>>(m, "IntVector")
|
||||
.def(py::init<>())
|
||||
.def("clear", &std::vector<int>::clear)
|
||||
.def("pop_back", &std::vector<int>::pop_back)
|
||||
.def("__len__", [](const std::vector<int> &v) { return v.size(); })
|
||||
.def("__iter__", [](std::vector<int> &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/stl_bind.h>
|
||||
|
||||
PYBIND11_MAKE_OPAQUE(std::vector<int>);
|
||||
PYBIND11_MAKE_OPAQUE(std::map<std::string, double>);
|
||||
|
||||
// ...
|
||||
|
||||
// later in binding code:
|
||||
py::bind_vector<std::vector<int>>(m, "VectorInt");
|
||||
py::bind_map<std::map<std::string, double>>(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<int>`` 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<std::vector<int>>(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<int>`` 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.
|
||||
305
python/pybind11/docs/advanced/cast/strings.rst
Normal file
305
python/pybind11/docs/advanced/cast/strings.rst
Normal file
@@ -0,0 +1,305 @@
|
||||
Strings, bytes and Unicode conversions
|
||||
######################################
|
||||
|
||||
.. note::
|
||||
|
||||
This section discusses string handling in terms of Python 3 strings. For
|
||||
Python 2.7, replace all occurrences of ``str`` with ``unicode`` and
|
||||
``bytes`` with ``str``. Python 2.7 users may find it best to use ``from
|
||||
__future__ import unicode_literals`` to avoid unintentionally using ``str``
|
||||
instead of ``unicode``.
|
||||
|
||||
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 <http://utf8everywhere.org/>`_.
|
||||
|
||||
.. 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:: python
|
||||
|
||||
>>> 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. On Python 3, 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:: python
|
||||
|
||||
>>> 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::str py_s = PyUnicode_DecodeLatin1(s.data(), s.length());
|
||||
return py_s;
|
||||
}
|
||||
);
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
>>> str_output()
|
||||
'Send your résumé to Alice in HR'
|
||||
|
||||
The `Python C API
|
||||
<https://docs.python.org/3/c-api/unicode.html#built-in-codecs>`_ provides
|
||||
several built-in codecs.
|
||||
|
||||
|
||||
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:: python
|
||||
|
||||
>>> 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:: python
|
||||
|
||||
>>> 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 <windows.h>
|
||||
|
||||
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;
|
||||
}
|
||||
);
|
||||
|
||||
.. warning::
|
||||
|
||||
Wide character strings may not work as described on Python 2.7 or Python
|
||||
3.3 compiled with ``--enable-unicode=ucs2``.
|
||||
|
||||
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:: python
|
||||
|
||||
>>> 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:: python
|
||||
|
||||
>>> 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:: python
|
||||
|
||||
>>> 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:: python
|
||||
|
||||
>>> 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
|
||||
<http://unicode.org/reports/tr29/#Grapheme_Cluster_Boundaries>`_, 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!) <https://www.joelonsoftware.com/2003/10/08/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 <https://msdn.microsoft.com/en-ca/magazine/mt238407.aspx>`_
|
||||
1125
python/pybind11/docs/advanced/classes.rst
Normal file
1125
python/pybind11/docs/advanced/classes.rst
Normal file
File diff suppressed because it is too large
Load Diff
261
python/pybind11/docs/advanced/embedding.rst
Normal file
261
python/pybind11/docs/advanced/embedding.rst
Normal file
@@ -0,0 +1,261 @@
|
||||
.. _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.0)
|
||||
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 <pybind11/embed.h> // 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 <pybind11/embed.h>
|
||||
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 <pybind11/embed.h>
|
||||
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 <pybind11/embed.h>
|
||||
#include <iostream>
|
||||
|
||||
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::string>();
|
||||
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<int>();
|
||||
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 <pybind11/embed.h>
|
||||
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<int>();
|
||||
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 <pybind11/embed.h>
|
||||
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<int>() == 1);
|
||||
assert(locals["b"].cast<int>() == 2);
|
||||
|
||||
py::exec(R"(
|
||||
c = a + b
|
||||
message = fmt.format(a, b, c)
|
||||
)", py::globals(), locals);
|
||||
|
||||
assert(locals["c"].cast<int>() == 3);
|
||||
assert(locals["message"].cast<std::string>() == "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.
|
||||
142
python/pybind11/docs/advanced/exceptions.rst
Normal file
142
python/pybind11/docs/advanced/exceptions.rst
Normal file
@@ -0,0 +1,142 @@
|
||||
Exceptions
|
||||
##########
|
||||
|
||||
Built-in exception translation
|
||||
==============================
|
||||
|
||||
When C++ code invoked from Python throws an ``std::exception``, it is
|
||||
automatically converted into a Python ``Exception``. pybind11 defines multiple
|
||||
special exception classes that will map to different types of Python
|
||||
exceptions:
|
||||
|
||||
.. tabularcolumns:: |p{0.5\textwidth}|p{0.45\textwidth}|
|
||||
|
||||
+--------------------------------------+--------------------------------------+
|
||||
| C++ exception type | 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:`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::value_error` | ``ValueError`` (used to indicate |
|
||||
| | wrong value passed in |
|
||||
| | ``container.remove(...)``) |
|
||||
+--------------------------------------+--------------------------------------+
|
||||
| :class:`pybind11::key_error` | ``KeyError`` (used to indicate out |
|
||||
| | of bounds access in ``__getitem__``, |
|
||||
| | ``__setitem__`` in dict-like |
|
||||
| | objects, etc.) |
|
||||
+--------------------------------------+--------------------------------------+
|
||||
| :class:`pybind11::error_already_set` | Indicates that the Python exception |
|
||||
| | flag has already been set via Python |
|
||||
| | API calls from C++ code; this C++ |
|
||||
| | exception is used to propagate such |
|
||||
| | a Python exception back to Python. |
|
||||
+--------------------------------------+--------------------------------------+
|
||||
|
||||
When a Python function invoked from C++ throws an exception, it is converted
|
||||
into a C++ exception of type :class:`error_already_set` whose string payload
|
||||
contains a textual summary.
|
||||
|
||||
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.
|
||||
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<CppExp>(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``.
|
||||
|
||||
When more advanced exception translation is needed, the function
|
||||
``py::register_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 function 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).
|
||||
|
||||
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
|
||||
``PyErr_SetString`` to set a Python exception or ``ex(string)`` to set
|
||||
the python exception to a custom exception type (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
|
||||
|
||||
static py::exception<MyCustomException> exc(m, "MyCustomError");
|
||||
py::register_exception_translator([](std::exception_ptr p) {
|
||||
try {
|
||||
if (p) std::rethrow_exception(p);
|
||||
} catch (const MyCustomException &e) {
|
||||
exc(e.what());
|
||||
} catch (const OtherException &e) {
|
||||
PyErr_SetString(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::
|
||||
|
||||
You must call either ``PyErr_SetString`` or a custom exception's call
|
||||
operator (``exc(string)``) 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.
|
||||
507
python/pybind11/docs/advanced/functions.rst
Normal file
507
python/pybind11/docs/advanced/functions.rst
Normal file
@@ -0,0 +1,507 @@
|
||||
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 a 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, 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` | **Default policy.** 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. |
|
||||
+--------------------------------------------------+----------------------------------------------------------------------------+
|
||||
| :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()). You probably won't need to use this. |
|
||||
+--------------------------------------------------+----------------------------------------------------------------------------+
|
||||
|
||||
Return value policies can also be applied to properties:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
class_<MyClass>(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_<MyClass>(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<Nurse, Patient>``
|
||||
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.
|
||||
|
||||
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_<List>(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_<Nurse>(m, "Nurse")
|
||||
.def(py::init<Patient &>(), 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<T>`` 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<T>());
|
||||
|
||||
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<T1, T2, T3...>``. 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(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, 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 as long as the ``py::args`` and ``py::kwargs`` arguments are the last
|
||||
arguments accepted by the function.
|
||||
|
||||
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>("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_<SomeType>``), 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 = <SomeType object at 0x101b7b080>) -> 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>("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>("MyClass")
|
||||
.def("myFunction", py::arg("arg") = (SomeType *) nullptr);
|
||||
|
||||
.. _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<A,B>()``
|
||||
* Calling a method accepting a double with an integer argument
|
||||
* Calling a ``std::complex<float>`` 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 "<stdin>", line 1, in <module>
|
||||
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_<Dog>(m, "Dog").def(py::init<>());
|
||||
py::class_<Cat>(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 "<stdin>", line 1, in <module>
|
||||
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 <opaque>` types. Pointers to built-in types
|
||||
(``double *``, ``int *``, ...) and STL types (``std::vector<T> *``, ...; 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<T>``
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
306
python/pybind11/docs/advanced/misc.rst
Normal file
306
python/pybind11/docs/advanced/misc.rst
Normal file
@@ -0,0 +1,306 @@
|
||||
Miscellaneous
|
||||
#############
|
||||
|
||||
.. _macro_notes:
|
||||
|
||||
General notes regarding convenience macros
|
||||
==========================================
|
||||
|
||||
pybind11 provides a few convenience macros such as
|
||||
:func:`PYBIND11_DECLARE_HOLDER_TYPE` and ``PYBIND11_OVERLOAD_*``. 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_OVERLOAD(MyReturnType<T1, T2>, Class<T3, T4>, 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<T1, T2>;
|
||||
using ClassType = Class<T3, T4>;
|
||||
PYBIND11_OVERLOAD(ReturnType, ClassType, func);
|
||||
|
||||
// Version 2: using the PYBIND11_TYPE macro:
|
||||
PYBIND11_OVERLOAD(PYBIND11_TYPE(MyReturnType<T1, T2>),
|
||||
PYBIND11_TYPE(Class<T3, T4>), func)
|
||||
|
||||
The ``PYBIND11_MAKE_OPAQUE`` macro does *not* require the above workarounds.
|
||||
|
||||
.. _gil:
|
||||
|
||||
Global Interpreter Lock (GIL)
|
||||
=============================
|
||||
|
||||
When calling a C++ function from Python, the GIL is always held.
|
||||
The classes :class:`gil_scoped_release` and :class:`gil_scoped_acquire` can be
|
||||
used to acquire and release the global interpreter lock in the body of a C++
|
||||
function call. In this way, long-running C++ code can be parallelized using
|
||||
multiple Python threads. Taking :ref:`overriding_virtuals` as an example, this
|
||||
could be realized as follows (important changes highlighted):
|
||||
|
||||
.. code-block:: cpp
|
||||
:emphasize-lines: 8,9,31,32
|
||||
|
||||
class PyAnimal : public Animal {
|
||||
public:
|
||||
/* Inherit the constructors */
|
||||
using Animal::Animal;
|
||||
|
||||
/* Trampoline (need one for each virtual function) */
|
||||
std::string go(int n_times) {
|
||||
/* Acquire GIL before calling Python code */
|
||||
py::gil_scoped_acquire acquire;
|
||||
|
||||
PYBIND11_OVERLOAD_PURE(
|
||||
std::string, /* Return type */
|
||||
Animal, /* Parent class */
|
||||
go, /* Name of function */
|
||||
n_times /* Argument(s) */
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
PYBIND11_MODULE(example, m) {
|
||||
py::class_<Animal, PyAnimal> animal(m, "Animal");
|
||||
animal
|
||||
.def(py::init<>())
|
||||
.def("go", &Animal::go);
|
||||
|
||||
py::class_<Dog>(m, "Dog", animal)
|
||||
.def(py::init<>());
|
||||
|
||||
m.def("call_go", [](Animal *animal) -> std::string {
|
||||
/* 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<py::gil_scoped_release>());
|
||||
|
||||
|
||||
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> pet(m, "Pet");
|
||||
pet.def(py::init<const std::string &>())
|
||||
.def_readwrite("name", &Pet::name);
|
||||
|
||||
py::class_<Dog>(m, "Dog", pet /* <- specify parent */)
|
||||
.def(py::init<const std::string &>())
|
||||
.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_<Dog>``.
|
||||
However, it can be acquired as follows:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
py::object pet = (py::object) py::module::import("basic").attr("Pet");
|
||||
|
||||
py::class_<Dog>(m, "Dog", pet)
|
||||
.def(py::init<const std::string &>())
|
||||
.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_<Dog, Pet>(m, "Dog")
|
||||
.def(py::init<const std::string &>())
|
||||
.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 = (MyData *) py::get_shared_data("mydata");
|
||||
if (!data)
|
||||
data = (MyData *) 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 colelcted
|
||||
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 (at least version 5.9) 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");
|
||||
}
|
||||
|
||||
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
|
||||
13
python/pybind11/docs/advanced/pycpp/index.rst
Normal file
13
python/pybind11/docs/advanced/pycpp/index.rst
Normal file
@@ -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
|
||||
386
python/pybind11/docs/advanced/pycpp/numpy.rst
Normal file
386
python/pybind11/docs/advanced/pycpp/numpy.rst
Normal file
@@ -0,0 +1,386 @@
|
||||
.. _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_<Matrix>(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<float>::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;
|
||||
ssize_t itemsize;
|
||||
std::string format;
|
||||
ssize_t ndim;
|
||||
std::vector<ssize_t> shape;
|
||||
std::vector<ssize_t> 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 an 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_<Matrix>(m, "Matrix", py::buffer_protocol())
|
||||
.def("__init__", [](Matrix &m, py::buffer b) {
|
||||
typedef Eigen::Stride<Eigen::Dynamic, Eigen::Dynamic> Strides;
|
||||
|
||||
/* Request a buffer descriptor from Python */
|
||||
py::buffer_info info = b.request();
|
||||
|
||||
/* Some sanity checks ... */
|
||||
if (info.format != py::format_descriptor<Scalar>::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<Matrix, 0, Strides>(
|
||||
static_cast<Scalar *>(info.ptr), info.shape[0], info.shape[1], strides);
|
||||
|
||||
new (&m) 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<Scalar>::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<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<double> 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. Note that this feature requires the
|
||||
:file:`pybind11/numpy.h` header to be included.
|
||||
|
||||
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<double, py::array::c_style | py::array::forcecast> 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.
|
||||
|
||||
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 <pybind11/pybind11.h>
|
||||
#include <pybind11/numpy.h>
|
||||
|
||||
namespace py = pybind11;
|
||||
|
||||
py::array_t<double> add_arrays(py::array_t<double> input1, py::array_t<double> 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<double>(buf1.size);
|
||||
|
||||
py::buffer_info buf3 = result.request();
|
||||
|
||||
double *ptr1 = (double *) buf1.ptr,
|
||||
*ptr2 = (double *) buf2.ptr,
|
||||
*ptr3 = (double *) 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<T>`` template
|
||||
class offer an unchecked proxy object that can be used for this unchecked
|
||||
access through the ``unchecked<N>`` and ``mutable_unchecked<N>`` methods,
|
||||
where ``N`` gives the required dimensionality of the array:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
m.def("sum_3d", [](py::array_t<double> x) {
|
||||
auto r = x.unchecked<3>(); // x must have ndim = 3; can be non-writeable
|
||||
double sum = 0;
|
||||
for (ssize_t i = 0; i < r.shape(0); i++)
|
||||
for (ssize_t j = 0; j < r.shape(1); j++)
|
||||
for (ssize_t k = 0; k < r.shape(2); k++)
|
||||
sum += r(i, j, k);
|
||||
return sum;
|
||||
});
|
||||
m.def("increment_3d", [](py::array_t<double> x) {
|
||||
auto r = x.mutable_unchecked<3>(); // Will throw if ndim != 3 or flags.writeable is false
|
||||
for (ssize_t i = 0; i < r.shape(0); i++)
|
||||
for (ssize_t j = 0; j < r.shape(1); j++)
|
||||
for (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<float, 2>()``.
|
||||
|
||||
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<T>()``. 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``:
|
||||
|
||||
- ``r.ndim()`` returns the number of dimensions
|
||||
|
||||
- ``r.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 3 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)];
|
||||
170
python/pybind11/docs/advanced/pycpp/object.rst
Normal file
170
python/pybind11/docs/advanced/pycpp/object.rst
Normal file
@@ -0,0 +1,170 @@
|
||||
Python types
|
||||
############
|
||||
|
||||
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`.
|
||||
|
||||
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<MyClass *>();
|
||||
|
||||
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<MyClass>();
|
||||
|
||||
.. _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/
|
||||
144
python/pybind11/docs/advanced/pycpp/utilities.rst
Normal file
144
python/pybind11/docs/advanced/pycpp/utilities.rst
Normal file
@@ -0,0 +1,144 @@
|
||||
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 <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 <pybind11/iostream.h>
|
||||
|
||||
...
|
||||
|
||||
// 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();
|
||||
});
|
||||
|
||||
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 <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:: py
|
||||
|
||||
// Alternative: Call single function using call guard
|
||||
m.def("noisy_func", &call_noisy_function,
|
||||
py::call_guard<py::scoped_ostream_redirect,
|
||||
py::scoped_estream_redirect>());
|
||||
|
||||
The redirection can also be done in Python with the addition of a context
|
||||
manager, using the `py::add_ostream_redirect() <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 <pybind11/eval.h>
|
||||
|
||||
...
|
||||
|
||||
// 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<int>();
|
||||
|
||||
// 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<eval_statements>``.
|
||||
173
python/pybind11/docs/advanced/smart_ptrs.rst
Normal file
173
python/pybind11/docs/advanced/smart_ptrs.rst
Normal file
@@ -0,0 +1,173 @@
|
||||
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<Example> create_example() { return std::unique_ptr<Example>(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<Example> 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<Type>``, 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_<Example, std::shared_ptr<Example> /* <- 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>()) { }
|
||||
Child *get_child() { return child.get(); } /* Hint: ** DON'T DO THIS ** */
|
||||
private:
|
||||
std::shared_ptr<Child> child;
|
||||
};
|
||||
|
||||
PYBIND11_MODULE(example, m) {
|
||||
py::class_<Child, std::shared_ptr<Child>>(m, "Child");
|
||||
|
||||
py::class_<Parent, std::shared_ptr<Parent>>(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<Child> get_child() { return child; }
|
||||
|
||||
2. Adjust the definition of ``Child`` by specifying
|
||||
``std::enable_shared_from_this<T>`` (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<Child> { };
|
||||
|
||||
.. _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<T>);
|
||||
|
||||
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<T>, true);
|
||||
|
||||
if ``SmartPtr<T>`` 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<T>);
|
||||
|
||||
// Only needed if the type's `.get()` goes by another name
|
||||
namespace pybind11 { namespace detail {
|
||||
template <typename T>
|
||||
struct holder_helper<SmartPtr<T>> { // <-- specialization
|
||||
static const T *get(const SmartPtr<T> &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.
|
||||
293
python/pybind11/docs/basics.rst
Normal file
293
python/pybind11/docs/basics.rst
Normal file
@@ -0,0 +1,293 @@
|
||||
.. _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 Mac OS, 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 2015** and newer are supported since pybind11 relies
|
||||
on various C++11 language features that break older versions of Visual Studio.
|
||||
|
||||
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 <pybind11/pybind11.h>
|
||||
|
||||
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 <pybind11/pybind11.h>
|
||||
|
||||
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 which 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 <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`
|
||||
|
||||
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 2.7.10 (default, Aug 22 2015, 20:33:39)
|
||||
[GCC 4.2.1 Compatible Apple LLVM 7.0.0 (clang-700.0.59.1)] on darwin
|
||||
Type "help", "copyright", "credits" or "license" for more information.
|
||||
>>> import example
|
||||
>>> example.add(1, 2)
|
||||
3L
|
||||
>>>
|
||||
|
||||
.. _keyword_args:
|
||||
|
||||
Keyword arguments
|
||||
=================
|
||||
|
||||
With a simple modification code, 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.
|
||||
88
python/pybind11/docs/benchmark.py
Normal file
88
python/pybind11/docs/benchmark.py
Normal file
@@ -0,0 +1,88 @@
|
||||
import random
|
||||
import os
|
||||
import time
|
||||
import datetime as dt
|
||||
|
||||
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 += "class cl%03i;\n" % cl
|
||||
decl += '\n'
|
||||
|
||||
for cl in range(nclasses):
|
||||
decl += "class cl%03i {\n" % cl
|
||||
decl += "public:\n"
|
||||
bindings += ' py::class_<cl%03i>(m, "cl%03i")\n' % (cl, cl)
|
||||
for fn in range(nfns):
|
||||
ret = random.randint(0, nclasses - 1)
|
||||
params = [random.randint(0, nclasses - 1) for i in range(nargs)]
|
||||
decl += " cl%03i *fn_%03i(" % (ret, fn)
|
||||
decl += ", ".join("cl%03i *" % p for p in params)
|
||||
decl += ");\n"
|
||||
bindings += ' .def("fn_%03i", &cl%03i::fn_%03i)\n' % \
|
||||
(fn, cl, fn)
|
||||
decl += "};\n\n"
|
||||
bindings += ' ;\n'
|
||||
|
||||
result = "#include <pybind11/pybind11.h>\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 += "class cl%03i;\n" % cl
|
||||
decl += '\n'
|
||||
|
||||
for cl in range(nclasses):
|
||||
decl += "class cl%03i {\n" % cl
|
||||
decl += "public:\n"
|
||||
bindings += ' py::class_<cl%03i>("cl%03i")\n' % (cl, cl)
|
||||
for fn in range(nfns):
|
||||
ret = random.randint(0, nclasses - 1)
|
||||
params = [random.randint(0, nclasses - 1) for i in range(nargs)]
|
||||
decl += " cl%03i *fn_%03i(" % (ret, fn)
|
||||
decl += ", ".join("cl%03i *" % p for p in params)
|
||||
decl += ");\n"
|
||||
bindings += ' .def("fn_%03i", &cl%03i::fn_%03i, py::return_value_policy<py::manage_new_object>())\n' % \
|
||||
(fn, cl, fn)
|
||||
decl += "};\n\n"
|
||||
bindings += ' ;\n'
|
||||
|
||||
result = "#include <boost/python.hpp>\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(0, 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 ("}")
|
||||
97
python/pybind11/docs/benchmark.rst
Normal file
97
python/pybind11/docs/benchmark.rst
Normal file
@@ -0,0 +1,97 @@
|
||||
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_<cl034>(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
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user