d4Solver 1.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- d4solver-1.0.0/.gitignore +38 -0
- d4solver-1.0.0/.gitlab-ci.yml +140 -0
- d4solver-1.0.0/CMakeLists.txt +240 -0
- d4solver-1.0.0/PKG-INFO +225 -0
- d4solver-1.0.0/README.md +214 -0
- d4solver-1.0.0/build.sh +71 -0
- d4solver-1.0.0/py_d4/__init__.py +65 -0
- d4solver-1.0.0/py_d4/options.py +27 -0
- d4solver-1.0.0/py_d4/py.typed +1 -0
- d4solver-1.0.0/pyproject.toml +37 -0
- d4solver-1.0.0/scripts/generate_binding_hpp.py +235 -0
- d4solver-1.0.0/scripts/generate_bindings.py +287 -0
- d4solver-1.0.0/scripts/generate_runner_bindings.py +579 -0
- d4solver-1.0.0/scripts/generate_stubs.py +24 -0
- d4solver-1.0.0/src/Binding.hpp +51 -0
- d4solver-1.0.0/src/generated_bindings.cpp +246 -0
- d4solver-1.0.0/src/generated_runner_bindings.cpp +115 -0
- d4solver-1.0.0/src/wrapper.cpp +49 -0
- d4solver-1.0.0/tests/test_api.py +134 -0
- d4solver-1.0.0/tests/test_options.py +112 -0
- d4solver-1.0.0/tests/test_solver.py +66 -0
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# --- CMake / Build directories ---
|
|
2
|
+
build/
|
|
3
|
+
cmake-build-debug/
|
|
4
|
+
cmake-build-release/
|
|
5
|
+
.cmake/
|
|
6
|
+
CMakeFiles/
|
|
7
|
+
CMakeCache.txt
|
|
8
|
+
Makefile
|
|
9
|
+
cmake_install.cmake
|
|
10
|
+
|
|
11
|
+
# --- Compiled shared libraries / binaries ---
|
|
12
|
+
*.so
|
|
13
|
+
*.dylib
|
|
14
|
+
*.dll
|
|
15
|
+
*.pyd
|
|
16
|
+
py_d4/_py_d4*.so
|
|
17
|
+
|
|
18
|
+
# --- Python packaging & caching ---
|
|
19
|
+
__pycache__/
|
|
20
|
+
*.pyc
|
|
21
|
+
*.pyo
|
|
22
|
+
.pytest_cache/
|
|
23
|
+
.eggs/
|
|
24
|
+
.egg-info/
|
|
25
|
+
*.egg-info/
|
|
26
|
+
dist/
|
|
27
|
+
wheelhouse/
|
|
28
|
+
venv/
|
|
29
|
+
.venv/
|
|
30
|
+
env/
|
|
31
|
+
|
|
32
|
+
# --- IDEs and editor configurations ---
|
|
33
|
+
.vscode/
|
|
34
|
+
.idea/
|
|
35
|
+
*.swp
|
|
36
|
+
*.swo
|
|
37
|
+
*~
|
|
38
|
+
.DS_Store
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
stages:
|
|
2
|
+
- build
|
|
3
|
+
- deploy
|
|
4
|
+
|
|
5
|
+
build_wheels_linux:
|
|
6
|
+
stage: build
|
|
7
|
+
tags:
|
|
8
|
+
- linux
|
|
9
|
+
image: docker:24.0.7
|
|
10
|
+
services:
|
|
11
|
+
- name: docker:24.0.7-dind
|
|
12
|
+
alias: docker
|
|
13
|
+
variables:
|
|
14
|
+
DOCKER_HOST: "tcp://docker:2375"
|
|
15
|
+
DOCKER_TLS_CERTDIR: ""
|
|
16
|
+
DOCKER_DRIVER: overlay2
|
|
17
|
+
PIP_BREAK_SYSTEM_PACKAGES: "1"
|
|
18
|
+
CMAKE_BUILD_PARALLEL_LEVEL: "2"
|
|
19
|
+
CIBW_ENVIRONMENT_PASS_LINUX: "GITLAB_TOKEN_D4 GITLAB_TOKEN GITLAB_TOKEN_LOGICAL CMAKE_BUILD_PARALLEL_LEVEL D4_GIT_TAG D4_GIT_URL"
|
|
20
|
+
before_script:
|
|
21
|
+
# Détection automatique du socket Docker local ou du service TCP
|
|
22
|
+
- |
|
|
23
|
+
if ! docker info >/dev/null 2>&1; then
|
|
24
|
+
echo "Docker TCP host ($DOCKER_HOST) unreachable, unsetting DOCKER_HOST to use local socket..."
|
|
25
|
+
unset DOCKER_HOST
|
|
26
|
+
fi
|
|
27
|
+
for i in $(seq 1 30); do
|
|
28
|
+
docker info >/dev/null 2>&1 && break || sleep 1
|
|
29
|
+
done
|
|
30
|
+
# Configuration simplifiée des jetons et tags
|
|
31
|
+
- |
|
|
32
|
+
export GITLAB_TOKEN_D4="${GITLAB_TOKEN_LOGICAL:-$CI_JOB_TOKEN}"
|
|
33
|
+
export GITLAB_TOKEN="${GITLAB_TOKEN_LOGICAL:-$CI_JOB_TOKEN}"
|
|
34
|
+
if [ -n "$CI_COMMIT_TAG" ]; then
|
|
35
|
+
export D4_GIT_TAG="${GITHUB_D4_TAG:-$CI_COMMIT_TAG}"
|
|
36
|
+
if echo "$D4_GIT_TAG" | grep -q '^[0-9]'; then
|
|
37
|
+
export D4_GIT_TAG="v$D4_GIT_TAG"
|
|
38
|
+
fi
|
|
39
|
+
export D4_GIT_URL="${GITHUB_D4_URL:-$D4_GIT_URL}"
|
|
40
|
+
else
|
|
41
|
+
export D4_GIT_TAG="master"
|
|
42
|
+
unset D4_GIT_URL
|
|
43
|
+
fi
|
|
44
|
+
- apk add --no-cache python3 py3-pip
|
|
45
|
+
script:
|
|
46
|
+
- |
|
|
47
|
+
if [ -n "$CI_COMMIT_TAG" ]; then
|
|
48
|
+
VERSION=$(echo "$CI_COMMIT_TAG" | sed 's/^v//')
|
|
49
|
+
echo "GitLab Tag detected: $CI_COMMIT_TAG -> Version: $VERSION"
|
|
50
|
+
VERSION="$VERSION" python3 -c "import re, os; p = 'pyproject.toml'; c = open(p).read(); open(p, 'w').write(re.sub(r'^version\s*=\s*\"[^\"]+\"', f'version = \"{os.environ[\"VERSION\"]}\"', c, count=1, flags=re.M))"
|
|
51
|
+
echo "Updated pyproject.toml version to $VERSION"
|
|
52
|
+
fi
|
|
53
|
+
- pip install cibuildwheel
|
|
54
|
+
- cibuildwheel --output-dir wheelhouse
|
|
55
|
+
artifacts:
|
|
56
|
+
paths:
|
|
57
|
+
- wheelhouse/
|
|
58
|
+
expire_in: 1 week
|
|
59
|
+
|
|
60
|
+
build_wheels_windows:
|
|
61
|
+
stage: build
|
|
62
|
+
tags:
|
|
63
|
+
- windows
|
|
64
|
+
- msvc
|
|
65
|
+
variables:
|
|
66
|
+
CMAKE_TOOLCHAIN_FILE: "C:/vcpkg/scripts/buildsystems/vcpkg.cmake"
|
|
67
|
+
CMAKE_BUILD_PARALLEL_LEVEL: "2"
|
|
68
|
+
CIBW_ENVIRONMENT_PASS_WINDOWS: "GITLAB_TOKEN_D4 GITLAB_TOKEN GITLAB_TOKEN_LOGICAL CMAKE_TOOLCHAIN_FILE CMAKE_BUILD_PARALLEL_LEVEL CIBW_REPAIR_WHEEL_COMMAND_WINDOWS D4_GIT_TAG D4_GIT_URL"
|
|
69
|
+
before_script:
|
|
70
|
+
- '$PROJECT_DIR = (Get-ChildItem -Path "C:\GitLab-Runner\builds" -Filter "pyproject.toml" -Recurse -File | Select-Object -First 1).DirectoryName'
|
|
71
|
+
- 'cd $PROJECT_DIR'
|
|
72
|
+
- '$env:GITLAB_TOKEN_LOGICAL = "glpat-eHavaAslBimi87EABf6MIW86MQp1OjJpdgk.01.0z0dxf0ko"'
|
|
73
|
+
- '$env:GITLAB_TOKEN_D4 = "glpat-eHavaAslBimi87EABf6MIW86MQp1OjJpdgk.01.0z0dxf0ko"'
|
|
74
|
+
- '$env:GITLAB_TOKEN = "glpat-eHavaAslBimi87EABf6MIW86MQp1OjJpdgk.01.0z0dxf0ko"'
|
|
75
|
+
- '$env:CMAKE_TOOLCHAIN_FILE = "C:/vcpkg/scripts/buildsystems/vcpkg.cmake"'
|
|
76
|
+
- '$env:CIBW_REPAIR_WHEEL_COMMAND_WINDOWS = "delvewheel repair --add-path C:\vcpkg\installed\x64-windows\bin -w {dest_dir} {wheel}"'
|
|
77
|
+
- '$env:D4_GIT_TAG = if ($env:CI_COMMIT_TAG) { if ($env:GITHUB_D4_TAG) { $env:GITHUB_D4_TAG } else { $env:CI_COMMIT_TAG } } else { "master" }'
|
|
78
|
+
- 'if ($env:CI_COMMIT_TAG -and ($env:D4_GIT_TAG -match "^[0-9]")) { $env:D4_GIT_TAG = "v" + $env:D4_GIT_TAG }'
|
|
79
|
+
- '$env:D4_GIT_URL = if ($env:CI_COMMIT_TAG) { if ($env:GITHUB_D4_URL) { $env:GITHUB_D4_URL } else { $env:D4_GIT_URL } } else { $null }'
|
|
80
|
+
- 'if ($env:CI_COMMIT_TAG) { $env:VERSION = $env:CI_COMMIT_TAG -replace "^v", ""; Write-Host "GitLab Tag detected: $env:CI_COMMIT_TAG -> Version: $env:VERSION"; python -c "import re, os; p = \"pyproject.toml\"; c = open(p).read(); open(p, \"w\").write(re.sub(r\"^version\s*=\s*\\\"[^\\\"]+\\\"\", f\"version = \\\"{os.environ[\\\"VERSION\\\"]}\\\"\", c, count=1, flags=re.M))" }'
|
|
81
|
+
- python -m pip install cibuildwheel
|
|
82
|
+
script:
|
|
83
|
+
- '$PROJECT_DIR = (Get-ChildItem -Path "C:\GitLab-Runner\builds" -Filter "pyproject.toml" -Recurse -File | Select-Object -First 1).DirectoryName'
|
|
84
|
+
- 'cd $PROJECT_DIR'
|
|
85
|
+
- '$env:GITLAB_TOKEN_LOGICAL = "glpat-eHavaAslBimi87EABf6MIW86MQp1OjJpdgk.01.0z0dxf0ko"'
|
|
86
|
+
- '$env:GITLAB_TOKEN_D4 = "glpat-eHavaAslBimi87EABf6MIW86MQp1OjJpdgk.01.0z0dxf0ko"'
|
|
87
|
+
- '$env:GITLAB_TOKEN = "glpat-eHavaAslBimi87EABf6MIW86MQp1OjJpdgk.01.0z0dxf0ko"'
|
|
88
|
+
- '$env:CMAKE_TOOLCHAIN_FILE = "C:/vcpkg/scripts/buildsystems/vcpkg.cmake"'
|
|
89
|
+
- '$env:CIBW_REPAIR_WHEEL_COMMAND_WINDOWS = "delvewheel repair --add-path C:\vcpkg\installed\x64-windows\bin -w {dest_dir} {wheel}"'
|
|
90
|
+
- '$env:D4_GIT_TAG = if ($env:CI_COMMIT_TAG) { if ($env:GITHUB_D4_TAG) { $env:GITHUB_D4_TAG } else { $env:CI_COMMIT_TAG } } else { "master" }'
|
|
91
|
+
- 'if ($env:CI_COMMIT_TAG -and ($env:D4_GIT_TAG -match "^[0-9]")) { $env:D4_GIT_TAG = "v" + $env:D4_GIT_TAG }'
|
|
92
|
+
- '$env:D4_GIT_URL = if ($env:CI_COMMIT_TAG) { if ($env:GITHUB_D4_URL) { $env:GITHUB_D4_URL } else { $env:D4_GIT_URL } } else { $null }'
|
|
93
|
+
- 'cibuildwheel --output-dir wheelhouse'
|
|
94
|
+
artifacts:
|
|
95
|
+
paths:
|
|
96
|
+
- wheelhouse/
|
|
97
|
+
expire_in: 1 week
|
|
98
|
+
|
|
99
|
+
build_sdist:
|
|
100
|
+
stage: build
|
|
101
|
+
tags:
|
|
102
|
+
- linux
|
|
103
|
+
image: python:3.10-slim
|
|
104
|
+
script:
|
|
105
|
+
- pip install build
|
|
106
|
+
- |
|
|
107
|
+
if [ -n "$CI_COMMIT_TAG" ]; then
|
|
108
|
+
VERSION=$(echo "$CI_COMMIT_TAG" | sed 's/^v//')
|
|
109
|
+
echo "GitLab Tag detected: $CI_COMMIT_TAG -> Version: $VERSION"
|
|
110
|
+
VERSION="$VERSION" python3 -c "import re, os; p = 'pyproject.toml'; c = open(p).read(); open(p, 'w').write(re.sub(r'^version\s*=\s*\"[^\"]+\"', f'version = \"{os.environ[\"VERSION\"]}\"', c, count=1, flags=re.M))"
|
|
111
|
+
echo "Updated pyproject.toml version to $VERSION"
|
|
112
|
+
fi
|
|
113
|
+
- python3 -m build --sdist --outdir wheelhouse/
|
|
114
|
+
artifacts:
|
|
115
|
+
paths:
|
|
116
|
+
- wheelhouse/
|
|
117
|
+
expire_in: 1 week
|
|
118
|
+
|
|
119
|
+
publish_to_gitlab:
|
|
120
|
+
stage: deploy
|
|
121
|
+
tags:
|
|
122
|
+
- linux
|
|
123
|
+
image: python:3.10-slim
|
|
124
|
+
script:
|
|
125
|
+
- pip install twine
|
|
126
|
+
- TWINE_PASSWORD=${CI_JOB_TOKEN} TWINE_USERNAME=gitlab-ci-token python -m twine upload --verbose --repository-url ${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/pypi wheelhouse/*
|
|
127
|
+
only:
|
|
128
|
+
- tags
|
|
129
|
+
|
|
130
|
+
publish_to_pypi:
|
|
131
|
+
stage: deploy
|
|
132
|
+
tags:
|
|
133
|
+
- linux
|
|
134
|
+
image: python:3.10-slim
|
|
135
|
+
script:
|
|
136
|
+
- pip install twine
|
|
137
|
+
- TWINE_USERNAME=__token__ TWINE_PASSWORD=${PYPI_API_TOKEN} python -m twine upload --verbose wheelhouse/*
|
|
138
|
+
rules:
|
|
139
|
+
- if: $CI_COMMIT_TAG
|
|
140
|
+
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
cmake_minimum_required(VERSION 3.15)
|
|
2
|
+
project(py-d4 LANGUAGES C CXX)
|
|
3
|
+
|
|
4
|
+
set(CMAKE_CXX_STANDARD 20)
|
|
5
|
+
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
|
6
|
+
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
|
|
7
|
+
|
|
8
|
+
if(MSVC)
|
|
9
|
+
add_compile_definitions(NOMINMAX)
|
|
10
|
+
add_compile_definitions(popen=_popen)
|
|
11
|
+
add_compile_definitions(pclose=_pclose)
|
|
12
|
+
endif()
|
|
13
|
+
|
|
14
|
+
if(POLICY CMP0097)
|
|
15
|
+
cmake_policy(SET CMP0097 NEW)
|
|
16
|
+
endif()
|
|
17
|
+
|
|
18
|
+
# -----------------------------------------------------------------------------
|
|
19
|
+
# 1. Fetch (or reuse) D4
|
|
20
|
+
# -----------------------------------------------------------------------------
|
|
21
|
+
include(FetchContent)
|
|
22
|
+
set(FETCHCONTENT_BASE_DIR "${CMAKE_CURRENT_BINARY_DIR}/_deps" CACHE PATH "Directory for FetchContent" FORCE)
|
|
23
|
+
|
|
24
|
+
# Allow overriding with a local D4 source directory (avoids network fetch)
|
|
25
|
+
# Usage: cmake -DD4_LOCAL_PATH=/path/to/d4 ..
|
|
26
|
+
set(D4_LOCAL_PATH "" CACHE PATH "Path to a local D4 source directory (skips FetchContent clone)")
|
|
27
|
+
|
|
28
|
+
# Allow overriding with a local optree source directory (avoids network fetch)
|
|
29
|
+
# Usage: cmake -DOPTREE_LOCAL_PATH=/path/to/optree .. or export OPTREE_LOCAL_PATH=/path/to/optree
|
|
30
|
+
set(OPTREE_LOCAL_PATH "" CACHE PATH "Path to a local optree source directory")
|
|
31
|
+
if (DEFINED ENV{OPTREE_LOCAL_PATH} AND NOT OPTREE_LOCAL_PATH)
|
|
32
|
+
set(OPTREE_LOCAL_PATH "$ENV{OPTREE_LOCAL_PATH}")
|
|
33
|
+
endif()
|
|
34
|
+
|
|
35
|
+
if (D4_LOCAL_PATH AND EXISTS "${D4_LOCAL_PATH}/CMakeLists.txt")
|
|
36
|
+
message(STATUS "Using local D4 source at: ${D4_LOCAL_PATH}")
|
|
37
|
+
FetchContent_Declare(
|
|
38
|
+
d4
|
|
39
|
+
SOURCE_DIR "${D4_LOCAL_PATH}"
|
|
40
|
+
)
|
|
41
|
+
else()
|
|
42
|
+
# Propagate token to optree (D4's internal dependency) environment variable
|
|
43
|
+
if (DEFINED ENV{GITLAB_TOKEN_LOGICAL} AND NOT DEFINED ENV{GITLAB_TOKEN})
|
|
44
|
+
set(ENV{GITLAB_TOKEN} "$ENV{GITLAB_TOKEN_LOGICAL}")
|
|
45
|
+
elseif (DEFINED ENV{GITLAB_TOKEN_D4} AND NOT DEFINED ENV{GITLAB_TOKEN})
|
|
46
|
+
set(ENV{GITLAB_TOKEN} "$ENV{GITLAB_TOKEN_D4}")
|
|
47
|
+
endif()
|
|
48
|
+
|
|
49
|
+
if (DEFINED ENV{GITLAB_TOKEN} AND NOT DEFINED ENV{GITLAB_TOKEN_LOGICAL})
|
|
50
|
+
set(ENV{GITLAB_TOKEN_LOGICAL} "$ENV{GITLAB_TOKEN}")
|
|
51
|
+
elseif (DEFINED ENV{GITLAB_TOKEN_D4} AND NOT DEFINED ENV{GITLAB_TOKEN_LOGICAL})
|
|
52
|
+
set(ENV{GITLAB_TOKEN_LOGICAL} "$ENV{GITLAB_TOKEN_D4}")
|
|
53
|
+
endif()
|
|
54
|
+
|
|
55
|
+
# Inject token or custom URL from environment variable if available
|
|
56
|
+
if (DEFINED ENV{D4_GIT_URL} AND NOT "$ENV{D4_GIT_URL}" STREQUAL "")
|
|
57
|
+
set(D4_GIT_URL "$ENV{D4_GIT_URL}")
|
|
58
|
+
message(STATUS "Using custom D4 Git URL from environment variable: ${D4_GIT_URL}")
|
|
59
|
+
elseif (DEFINED ENV{GITLAB_TOKEN_D4})
|
|
60
|
+
set(D4_GIT_URL "https://oauth2:$ENV{GITLAB_TOKEN_D4}@gitlab.univ-artois.fr/logical/d4.git")
|
|
61
|
+
message(STATUS "Using authenticated GitLab URL for D4 (token from env GITLAB_TOKEN_D4)")
|
|
62
|
+
elseif (DEFINED ENV{GITLAB_TOKEN_LOGICAL})
|
|
63
|
+
set(D4_GIT_URL "https://oauth2:$ENV{GITLAB_TOKEN_LOGICAL}@gitlab.univ-artois.fr/logical/d4.git")
|
|
64
|
+
message(STATUS "Using authenticated GitLab URL for D4 (token from env GITLAB_TOKEN_LOGICAL)")
|
|
65
|
+
else()
|
|
66
|
+
set(D4_GIT_URL "https://gitlab.univ-artois.fr/logical/d4.git")
|
|
67
|
+
message(STATUS "No GITLAB_TOKEN_D4 or GITLAB_TOKEN_LOGICAL env variable set — using unauthenticated URL for D4")
|
|
68
|
+
endif()
|
|
69
|
+
|
|
70
|
+
# Allow overriding D4 git tag/branch via CMake variable or environment variable
|
|
71
|
+
set(D4_GIT_TAG "master" CACHE STRING "Git tag/branch for fetching D4")
|
|
72
|
+
if (DEFINED ENV{D4_GIT_TAG} AND NOT "$ENV{D4_GIT_TAG}" STREQUAL "")
|
|
73
|
+
set(D4_GIT_TAG "$ENV{D4_GIT_TAG}")
|
|
74
|
+
endif()
|
|
75
|
+
message(STATUS "D4 Git tag/branch set to: ${D4_GIT_TAG}")
|
|
76
|
+
|
|
77
|
+
FetchContent_Declare(
|
|
78
|
+
d4
|
|
79
|
+
GIT_REPOSITORY ${D4_GIT_URL}
|
|
80
|
+
GIT_TAG ${D4_GIT_TAG}
|
|
81
|
+
GIT_SHALLOW TRUE
|
|
82
|
+
GIT_SUBMODULES ""
|
|
83
|
+
)
|
|
84
|
+
endif()
|
|
85
|
+
|
|
86
|
+
# Fetch, patch and integrate D4 as a subdirectory.
|
|
87
|
+
FetchContent_GetProperties(d4)
|
|
88
|
+
if(NOT d4_POPULATED)
|
|
89
|
+
# CMP0169: FetchContent_Populate with declared details was deprecated in
|
|
90
|
+
# CMake 3.30. We set the policy to OLD so that the call below continues
|
|
91
|
+
# to work until we can migrate to a cleaner solution.
|
|
92
|
+
if(POLICY CMP0169)
|
|
93
|
+
cmake_policy(SET CMP0169 OLD)
|
|
94
|
+
endif()
|
|
95
|
+
FetchContent_Populate(d4)
|
|
96
|
+
|
|
97
|
+
set(D4_CMAKELISTS "${d4_SOURCE_DIR}/CMakeLists.txt")
|
|
98
|
+
if(EXISTS "${D4_CMAKELISTS}")
|
|
99
|
+
file(READ "${D4_CMAKELISTS}" _d4_cmake)
|
|
100
|
+
|
|
101
|
+
# Patch: do not force static ZLIB inside the python wrapper (which compiles a shared module)
|
|
102
|
+
string(REPLACE
|
|
103
|
+
"set(ZLIB_USE_STATIC_LIBS ON)"
|
|
104
|
+
"if(CMAKE_PROJECT_NAME STREQUAL \"D4\")\n set(ZLIB_USE_STATIC_LIBS ON)\n endif()"
|
|
105
|
+
_d4_cmake "${_d4_cmake}")
|
|
106
|
+
|
|
107
|
+
# Patch: use local optree source directory if specified and exists
|
|
108
|
+
if (OPTREE_LOCAL_PATH AND EXISTS "${OPTREE_LOCAL_PATH}/CMakeLists.txt")
|
|
109
|
+
string(REPLACE
|
|
110
|
+
"GIT_REPOSITORY \${OPTREE_GIT_URL}\n GIT_TAG main"
|
|
111
|
+
"SOURCE_DIR \"${OPTREE_LOCAL_PATH}\""
|
|
112
|
+
_d4_cmake "${_d4_cmake}")
|
|
113
|
+
endif()
|
|
114
|
+
|
|
115
|
+
# Patch: remove unused variable 'nb' in DpllStyleMethod.hpp to avoid compiler warnings
|
|
116
|
+
set(D4_DPLL_STYLE_METHOD "${d4_SOURCE_DIR}/src/methods/DpllStyleMethod.hpp")
|
|
117
|
+
if(EXISTS "${D4_DPLL_STYLE_METHOD}")
|
|
118
|
+
file(READ "${D4_DPLL_STYLE_METHOD}" _dpll_content)
|
|
119
|
+
string(REPLACE
|
|
120
|
+
"unsigned nb = 0, sizeAssum = m_solver->sizeAssumption();"
|
|
121
|
+
"unsigned sizeAssum = m_solver->sizeAssumption();"
|
|
122
|
+
_dpll_content "${_dpll_content}")
|
|
123
|
+
string(REPLACE
|
|
124
|
+
"int tmp = m_nbCallCall;"
|
|
125
|
+
"// int tmp = m_nbCallCall;"
|
|
126
|
+
_dpll_content "${_dpll_content}")
|
|
127
|
+
file(WRITE "${D4_DPLL_STYLE_METHOD}" "${_dpll_content}")
|
|
128
|
+
endif()
|
|
129
|
+
|
|
130
|
+
file(WRITE "${D4_CMAKELISTS}" "${_d4_cmake}")
|
|
131
|
+
endif()
|
|
132
|
+
|
|
133
|
+
# Patch D4 option headers to fix include paths on older branches (like master)
|
|
134
|
+
file(GLOB _option_files "${d4_SOURCE_DIR}/src/options/methods/Option*.hpp")
|
|
135
|
+
foreach(_opt_file ${_option_files})
|
|
136
|
+
if(EXISTS "${_opt_file}")
|
|
137
|
+
file(READ "${_opt_file}" _opt_content)
|
|
138
|
+
string(REPLACE
|
|
139
|
+
"options/solvers/OptionSolver.hpp"
|
|
140
|
+
"solvers/OptionSolver.hpp"
|
|
141
|
+
_opt_content "${_opt_content}")
|
|
142
|
+
file(WRITE "${_opt_file}" "${_opt_content}")
|
|
143
|
+
endif()
|
|
144
|
+
endforeach()
|
|
145
|
+
|
|
146
|
+
add_subdirectory(${d4_SOURCE_DIR} ${d4_BINARY_DIR})
|
|
147
|
+
add_subdirectory("${d4_SOURCE_DIR}/3rdParty/cadical" "${CMAKE_CURRENT_BINARY_DIR}/cadical_build")
|
|
148
|
+
target_compile_definitions(cadical PRIVATE NCLOSEFROM)
|
|
149
|
+
endif()
|
|
150
|
+
|
|
151
|
+
message(STATUS "D4 source dir: ${d4_SOURCE_DIR}")
|
|
152
|
+
message(STATUS "D4 binary dir: ${d4_BINARY_DIR}")
|
|
153
|
+
|
|
154
|
+
# -----------------------------------------------------------------------------
|
|
155
|
+
# 2. Setup Nanobind & Python
|
|
156
|
+
# -----------------------------------------------------------------------------
|
|
157
|
+
find_package(Python 3.8 COMPONENTS Interpreter Development.Module REQUIRED)
|
|
158
|
+
|
|
159
|
+
find_package(nanobind CONFIG QUIET)
|
|
160
|
+
if (NOT nanobind_FOUND)
|
|
161
|
+
message(STATUS "nanobind not found. Fetching it...")
|
|
162
|
+
FetchContent_Declare(
|
|
163
|
+
nanobind
|
|
164
|
+
GIT_REPOSITORY https://github.com/wjakob/nanobind.git
|
|
165
|
+
GIT_TAG v2.0.0
|
|
166
|
+
)
|
|
167
|
+
FetchContent_MakeAvailable(nanobind)
|
|
168
|
+
endif()
|
|
169
|
+
|
|
170
|
+
# Find or fetch nlohmann_json
|
|
171
|
+
find_package(nlohmann_json QUIET)
|
|
172
|
+
if (NOT nlohmann_json_FOUND)
|
|
173
|
+
message(STATUS "nlohmann_json not found. Fetching it...")
|
|
174
|
+
FetchContent_Declare(
|
|
175
|
+
json
|
|
176
|
+
GIT_REPOSITORY https://github.com/nlohmann/json.git
|
|
177
|
+
GIT_TAG v3.11.3
|
|
178
|
+
)
|
|
179
|
+
FetchContent_MakeAvailable(json)
|
|
180
|
+
endif()
|
|
181
|
+
|
|
182
|
+
# -----------------------------------------------------------------------------
|
|
183
|
+
# 3. Auto-generate C++ definitions and Python bindings
|
|
184
|
+
# -----------------------------------------------------------------------------
|
|
185
|
+
add_custom_target(
|
|
186
|
+
py_d4_generate_bindings
|
|
187
|
+
COMMAND "${Python_EXECUTABLE}" "${CMAKE_CURRENT_SOURCE_DIR}/scripts/generate_binding_hpp.py" "${d4_SOURCE_DIR}"
|
|
188
|
+
COMMAND "${Python_EXECUTABLE}" "${CMAKE_CURRENT_SOURCE_DIR}/scripts/generate_bindings.py" "${d4_SOURCE_DIR}"
|
|
189
|
+
COMMAND "${Python_EXECUTABLE}" "${CMAKE_CURRENT_SOURCE_DIR}/scripts/generate_runner_bindings.py" "${d4_SOURCE_DIR}"
|
|
190
|
+
COMMENT "Auto-generating options C++ definitions and Python bindings..."
|
|
191
|
+
VERBATIM
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
# -----------------------------------------------------------------------------
|
|
195
|
+
# 4. Define Nanobind Module
|
|
196
|
+
# -----------------------------------------------------------------------------
|
|
197
|
+
nanobind_add_module(
|
|
198
|
+
_py_d4
|
|
199
|
+
NB_STATIC
|
|
200
|
+
src/wrapper.cpp
|
|
201
|
+
src/generated_bindings.cpp
|
|
202
|
+
src/generated_runner_bindings.cpp
|
|
203
|
+
"${d4_SOURCE_DIR}/api/solver/Solver.cpp"
|
|
204
|
+
"${d4_SOURCE_DIR}/c++/parser/ParserDimacs.cpp"
|
|
205
|
+
"${d4_SOURCE_DIR}/c++/parser/ParserCircuit.cpp"
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
# Ensure bindings are generated before compiling the module
|
|
209
|
+
add_dependencies(_py_d4 py_d4_generate_bindings)
|
|
210
|
+
|
|
211
|
+
# Link against the d4::d4 target, nlohmann_json, and cadical
|
|
212
|
+
target_link_libraries(_py_d4 PRIVATE d4::d4 nlohmann_json::nlohmann_json cadical)
|
|
213
|
+
|
|
214
|
+
if(WIN32)
|
|
215
|
+
# Link psapi for GetProcessMemoryInfo used in CaDiCaL
|
|
216
|
+
target_link_libraries(_py_d4 PRIVATE psapi)
|
|
217
|
+
|
|
218
|
+
# Link gmpxx on Windows (needed for GMP ostream operators)
|
|
219
|
+
find_library(GMPXX_LIBRARY NAMES gmpxx libgmpxx REQUIRED)
|
|
220
|
+
target_link_libraries(_py_d4 PRIVATE ${GMPXX_LIBRARY})
|
|
221
|
+
endif()
|
|
222
|
+
|
|
223
|
+
# Include paths: our src/ + D4's source tree (for headers)
|
|
224
|
+
target_include_directories(_py_d4 PRIVATE
|
|
225
|
+
"${CMAKE_CURRENT_SOURCE_DIR}/src"
|
|
226
|
+
"${d4_SOURCE_DIR}/src"
|
|
227
|
+
)
|
|
228
|
+
install(TARGETS _py_d4 DESTINATION py_d4)
|
|
229
|
+
|
|
230
|
+
# Generate Python autocomplete/stub files (.pyi) post-build
|
|
231
|
+
add_custom_command(
|
|
232
|
+
TARGET _py_d4 POST_BUILD
|
|
233
|
+
COMMAND "${Python_EXECUTABLE}" "${CMAKE_CURRENT_SOURCE_DIR}/scripts/generate_stubs.py" _py_d4 "${CMAKE_CURRENT_BINARY_DIR}"
|
|
234
|
+
WORKING_DIRECTORY $<TARGET_FILE_DIR:_py_d4>
|
|
235
|
+
COMMENT "Generating autocomplete/type stub file (_py_d4.pyi)..."
|
|
236
|
+
VERBATIM
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
# Install the generated stub file
|
|
240
|
+
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/_py_d4.pyi" DESTINATION py_d4 OPTIONAL)
|
d4solver-1.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
Metadata-Version: 2.2
|
|
2
|
+
Name: d4Solver
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Python bindings for the D4 model counter and compiler
|
|
5
|
+
Author-Email: Logical Team <contact@univ-artois.fr>
|
|
6
|
+
Classifier: Programming Language :: C++
|
|
7
|
+
Classifier: Programming Language :: Python :: 3
|
|
8
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
9
|
+
Requires-Python: >=3.9
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
|
|
12
|
+
# py-d4
|
|
13
|
+
|
|
14
|
+
Python bindings for the **D4** compiler and model counter, supporting option structures, CNF files, custom clause vectors, logic circuits, weighted model counting, and d-DNNF queries. Built with `nanobind` and `scikit-build-core`.
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## 🚀 Users Guide
|
|
19
|
+
|
|
20
|
+
### 1. Installation
|
|
21
|
+
|
|
22
|
+
Install the package directly from your GitLab PyPI package registry (typically configured in your CI or local `.pip/pip.conf`):
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
pip install d4Solver --extra-index-url https://gitlab.univ-artois.fr/api/v4/projects/<PROJECT_ID>/packages/pypi/simple
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### 2. Usage Examples
|
|
29
|
+
|
|
30
|
+
#### 2.1 Basic Model Counting
|
|
31
|
+
|
|
32
|
+
You can instantiate a `Solver` using a CNF filepath or raw Python clause structures (list of lists/tuples of literals, 1-based indexing):
|
|
33
|
+
|
|
34
|
+
```python
|
|
35
|
+
import py_d4
|
|
36
|
+
|
|
37
|
+
# 1. Option A: Load from a DIMACS CNF file
|
|
38
|
+
solver = py_d4.Solver("path/to/formula.cnf")
|
|
39
|
+
|
|
40
|
+
# 2. Option B: Pass raw Python clauses and total variable count
|
|
41
|
+
# Formula: (x1 or x2) and (not x2 or x3)
|
|
42
|
+
clauses = [
|
|
43
|
+
[1, 2],
|
|
44
|
+
[-2, 3]
|
|
45
|
+
]
|
|
46
|
+
nb_vars = 3
|
|
47
|
+
solver = py_d4.Solver(clauses, nb_vars)
|
|
48
|
+
|
|
49
|
+
# Run model counting
|
|
50
|
+
count_res = solver.count()
|
|
51
|
+
print(f"Number of models: {count_res.getResult()}") # Large integer as string (GMP support)
|
|
52
|
+
print(f"As Python integer: {count_res.getIntResult()}") # Safe python int
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
#### 2.2 Compilation to d-DNNF
|
|
56
|
+
|
|
57
|
+
`py-d4` can compile CNF formulas and circuits into Decision Diagram / Deterministic Decomposable Negation Normal Form (d-DNNF):
|
|
58
|
+
|
|
59
|
+
```python
|
|
60
|
+
import py_d4
|
|
61
|
+
|
|
62
|
+
clauses = [[1, 2], [-2, 3]]
|
|
63
|
+
solver = py_d4.Solver(clauses, 3)
|
|
64
|
+
|
|
65
|
+
# Compile
|
|
66
|
+
compile_res = solver.compile()
|
|
67
|
+
|
|
68
|
+
# Access compiled graph statistics
|
|
69
|
+
print(f"Nodes in d-DNNF: {compile_res.getNbNodes()}")
|
|
70
|
+
print(f"Edges in d-DNNF: {compile_res.getNbEdges()}")
|
|
71
|
+
|
|
72
|
+
# Get the compiled NNF circuit string (in standard d-DNNF format)
|
|
73
|
+
nnf_str = compile_res.getNNFString()
|
|
74
|
+
print("NNF String:\n", nnf_str)
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
#### 2.3 Querying the Compiled d-DNNF
|
|
78
|
+
|
|
79
|
+
Once compiled, you can run multiple queries (SAT checks or model counting) under different literal assumptions:
|
|
80
|
+
|
|
81
|
+
```python
|
|
82
|
+
# Check if the formula is satisfiable (no assumptions)
|
|
83
|
+
is_sat = compile_res.isSAT([]) # True/False
|
|
84
|
+
|
|
85
|
+
# Check satisfiability assuming x1 is True (1) and x2 is False (-2)
|
|
86
|
+
is_sat_under_assumptions = compile_res.isSAT([1, -2])
|
|
87
|
+
|
|
88
|
+
# Count models under literal assumptions
|
|
89
|
+
# E.g., model count assuming x1 is True
|
|
90
|
+
count_under_x1 = compile_res.count([1])
|
|
91
|
+
print(f"Models satisfying x1: {count_under_x1}")
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
#### 2.4 Weighted Model Counting (WMC)
|
|
95
|
+
|
|
96
|
+
You can assign weights to literals for exact weighted model counting:
|
|
97
|
+
|
|
98
|
+
```python
|
|
99
|
+
import py_d4
|
|
100
|
+
|
|
101
|
+
# Formula: (x1 or x2)
|
|
102
|
+
solver = py_d4.Solver([[1, 2]], nb_vars=2)
|
|
103
|
+
|
|
104
|
+
# Define weights for literals (keys are 1-based literals, values are string representations)
|
|
105
|
+
weights = {
|
|
106
|
+
1: "0.3",
|
|
107
|
+
-1: "0.7",
|
|
108
|
+
2: "0.4",
|
|
109
|
+
-2: "0.6"
|
|
110
|
+
}
|
|
111
|
+
# Set weights (WeightType can be FLOAT or GMP)
|
|
112
|
+
solver.setWeights(weights, py_d4.WeightType.FLOAT)
|
|
113
|
+
|
|
114
|
+
# Compute WMC directly
|
|
115
|
+
count_res = solver.count()
|
|
116
|
+
print("WMC Result:", float(count_res.getResult())) # Output: 0.58
|
|
117
|
+
|
|
118
|
+
# Or compile and query WMC under assumptions
|
|
119
|
+
compile_res = solver.compile()
|
|
120
|
+
print("WMC under x1=True:", float(compile_res.count([1]))) # Output: 0.3
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
#### 2.5 Logic Gates / Circuit Compilation
|
|
124
|
+
|
|
125
|
+
Instead of CNF, you can initialize the solver with logic gates to compile or count structured circuits:
|
|
126
|
+
|
|
127
|
+
```python
|
|
128
|
+
import py_d4
|
|
129
|
+
|
|
130
|
+
# Create a gate representing: x3 = x1 AND (not x2)
|
|
131
|
+
gate = py_d4.Gate()
|
|
132
|
+
gate.gateType = py_d4.GateType.AND
|
|
133
|
+
gate.inputs = [1, -2]
|
|
134
|
+
gate.output = 3
|
|
135
|
+
|
|
136
|
+
# Instantiate solver from gate specifications
|
|
137
|
+
solver = py_d4.Solver([gate], nb_vars=3)
|
|
138
|
+
print("Models for circuit:", solver.count().getIntResult())
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
#### 2.6 Solver Configuration & Options
|
|
142
|
+
|
|
143
|
+
Fine-tune the behavior of D4 by mutating native C++ option structures. Documentation and description of settings are exposed natively as Python docstrings:
|
|
144
|
+
|
|
145
|
+
```python
|
|
146
|
+
import py_d4
|
|
147
|
+
|
|
148
|
+
# Instantiate default options
|
|
149
|
+
opt = py_d4.OptionDpllStyleMethod()
|
|
150
|
+
|
|
151
|
+
# 1. Read Option Descriptions (C++ docstrings exposed to Python)
|
|
152
|
+
print(py_d4.OptionDpllStyleMethod.exploitModel.__doc__)
|
|
153
|
+
# Output: "If we exploit model during search"
|
|
154
|
+
|
|
155
|
+
# 2. Modify properties directly (with type safety)
|
|
156
|
+
opt.precision = 30
|
|
157
|
+
opt.exploitModel = False
|
|
158
|
+
opt.optionSolver.solverName = 0 # 0: glucose, 1: minisat
|
|
159
|
+
|
|
160
|
+
# 3. Modify nested option group settings
|
|
161
|
+
opt.optionBranchingHeuristic.freqDecay = 98
|
|
162
|
+
|
|
163
|
+
# 4. Pass options to the Solver
|
|
164
|
+
solver = py_d4.Solver("formula.cnf", opt)
|
|
165
|
+
|
|
166
|
+
# 5. Serialize options to/from standard Python Dictionaries
|
|
167
|
+
config_dict = py_d4.dump_options_to_dict(opt)
|
|
168
|
+
opt_restored = py_d4.load_options_from_dict(config_dict)
|
|
169
|
+
|
|
170
|
+
# 6. Serialize options to/from raw JSON Strings (via native C++ bindings)
|
|
171
|
+
json_str = py_d4._py_d4.dump_options_to_json(opt)
|
|
172
|
+
opt_restored_json = py_d4._py_d4.load_options_from_json(json_str)
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
---
|
|
176
|
+
|
|
177
|
+
## 🛠️ Developers Guide
|
|
178
|
+
|
|
179
|
+
### 1. Requirements
|
|
180
|
+
|
|
181
|
+
Before building locally, ensure the following system-level dependencies are installed on your host:
|
|
182
|
+
- C++20 compatible compiler (e.g. GCC >= 10, Clang >= 10)
|
|
183
|
+
- CMake (>= 3.15)
|
|
184
|
+
- GMP development headers (`libgmp-dev` / `gmp-devel`)
|
|
185
|
+
- Zlib development headers (`zlib1g-dev` / `zlib-devel`)
|
|
186
|
+
|
|
187
|
+
### 2. Local Build Pipeline
|
|
188
|
+
|
|
189
|
+
To compile the C++ extension module and deploy it directly inside your local Python package tree, run:
|
|
190
|
+
|
|
191
|
+
```bash
|
|
192
|
+
# 1. Provide your GitLab access token to clone the D4 dependency
|
|
193
|
+
export GITLAB_TOKEN_LOGICAL="your_gitlab_token"
|
|
194
|
+
|
|
195
|
+
# 2. Configure, generate bindings, compile, and deploy shared library
|
|
196
|
+
./build.sh
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
Alternatively, you can install the package in editable mode via pip:
|
|
200
|
+
```bash
|
|
201
|
+
pip install -e .
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
### 3. Re-Generating Bindings
|
|
205
|
+
|
|
206
|
+
If D4 option headers change, you can automatically parse the new C++ classes and inline constructor descriptions to rebuild the `nanobind` bindings:
|
|
207
|
+
|
|
208
|
+
```bash
|
|
209
|
+
python3 scripts/generate_bindings.py /path/to/local/d4
|
|
210
|
+
```
|
|
211
|
+
*(If `/path/to/local/d4` is omitted, the script automatically searches the CMake FetchContent directory).*
|
|
212
|
+
|
|
213
|
+
### 4. Running Tests
|
|
214
|
+
|
|
215
|
+
To execute the test suite and verify the integrity of the wrapper:
|
|
216
|
+
|
|
217
|
+
```bash
|
|
218
|
+
LD_PRELOAD=$(gcc -print-file-name=libstdc++.so.6) PYTHONPATH=. python3 tests/test_options.py
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
### 5. GitLab CI/CD Pipeline
|
|
222
|
+
|
|
223
|
+
The project includes an automated multi-stage `.gitlab-ci.yml` pipeline:
|
|
224
|
+
- **Build Stage**: Uses `cibuildwheel` to compile optimized, standalone Linux wheels for Python 3.8 to 3.13. It automatically routes the group-level `GITLAB_TOKEN_LOGICAL` CI variable to authenticate both the `py-d4` and internal `d4`/`optree` sub-project checkouts.
|
|
225
|
+
- **Deploy Stage**: Triggered on pushing Git tags. Automatically publishes the generated wheel packages directly to the GitLab project PyPI package registry using `twine`.
|