From e679797f0dd9214fe285f7992c755fb5364c08e7 Mon Sep 17 00:00:00 2001 From: Quentin Date: Fri, 20 Dec 2024 10:18:52 +0100 Subject: [PATCH 01/11] Added CI Workflow for publishing automatically --- .github/workflows/build_wheels.yml | 70 ++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 .github/workflows/build_wheels.yml diff --git a/.github/workflows/build_wheels.yml b/.github/workflows/build_wheels.yml new file mode 100644 index 0000000..86cedae --- /dev/null +++ b/.github/workflows/build_wheels.yml @@ -0,0 +1,70 @@ +name: Build and upload to PyPI + +on: + workflow_dispatch: + pull_request: + branches: + - main + push: + branches: + - main + +jobs: + build_wheels: + name: Build wheels on ${{ matrix.os }} + runs-on: ${{ matrix.os }} + strategy: + matrix: + # macos-13 is an intel runner, macos-14 is apple silicon + os: [ubuntu-latest, windows-latest, macos-13, macos-14] + + steps: + - uses: actions/checkout@v4 + + - name: Build wheels + uses: pypa/cibuildwheel@v2.22.0 + + - uses: actions/upload-artifact@v4 + with: + name: cibw-wheels-${{ matrix.os }}-${{ strategy.job-index }} + path: ./wheelhouse/*.whl + + build_sdist: + name: Build source distribution + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Build sdist + run: pipx run build --sdist + + - uses: actions/upload-artifact@v4 + with: + name: cibw-sdist + path: dist/*.tar.gz + + upload_pypi: + needs: [build_wheels, build_sdist] + runs-on: ubuntu-latest + environment: pypi + permissions: + id-token: write + if: github.event_name == 'release' && github.event.action == 'published' + # or, alternatively, upload to PyPI on every tag starting with 'v' (remove on: release above to use this) + #if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') + steps: + - uses: actions/download-artifact@v4 + with: + # unpacks all CIBW artifacts into dist/ + pattern: cibw-* + path: dist + merge-multiple: true + + - name: Generate artifact attestations + uses: actions/attest-build-provenance@v1.4.4 + with: + subject-path: "dist/*" + + - uses: pypa/gh-action-pypi-publish@release/v1 + #with: + # To test: repository-url: https://test.pypi.org/legacy/ \ No newline at end of file From 25338b70dbc4a6a02a00aaa42038bbe0663d27e7 Mon Sep 17 00:00:00 2001 From: Quentin Date: Fri, 20 Dec 2024 12:54:38 +0100 Subject: [PATCH 02/11] Adapting headers for crossplatform compile --- pyproject.toml | 2 +- radius_clustering/radius_clustering.py | 20 ++++++++++++---- radius_clustering/utils/mds3-util.h | 33 ++++++++++++++++++++++---- 3 files changed, 46 insertions(+), 9 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index c3374dc..480d149 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "radius-clustering" -version = "1.0.0" +version = "1.0.1" description = "A Clustering under radius constraints algorithm using minimum dominating sets" readme = "README.md" authors = [ diff --git a/radius_clustering/radius_clustering.py b/radius_clustering/radius_clustering.py index 8fd3cc0..60dfe36 100644 --- a/radius_clustering/radius_clustering.py +++ b/radius_clustering/radius_clustering.py @@ -10,7 +10,7 @@ import os import numpy as np -import scipy.spatial as sp_spatial +from sklearn.metrics import pairwise_distances from sklearn.base import BaseEstimator, ClusterMixin from sklearn.utils.validation import check_array @@ -38,7 +38,7 @@ class RadiusClustering(BaseEstimator, ClusterMixin): ----------- X : array-like, shape (n_samples, n_features) The input data. - centers : list + centers\_ : list The indices of the cluster centers. labels\_ : array-like, shape (n_samples,) The cluster labels for each point in the input data. @@ -50,6 +50,9 @@ def __init__(self, manner="approx", threshold=0.5): self.manner = manner self.threshold = threshold + def _check_symmetric(self, a, tol=1e-8): + return np.allclose(a, a.T, atol=tol) + def fit(self, X, y=None): """ Fit the MDS clustering model to the input data. @@ -87,10 +90,19 @@ def fit(self, X, y=None): self.X = check_array(X) # Create dist and adj matrices - dist_mat = sp_spatial.distance_matrix(self.X, self.X) + if not self._check_symmetric(self.X): + dist_mat = pairwise_distances(self.X, metric="euclidean") + else: + dist_mat = self.X adj_mask = np.triu((dist_mat <= self.threshold), k=1) self.nb_edges = np.sum(adj_mask) - self.edges = np.argwhere(adj_mask).astype(np.int32) + if self.nb_edges == 0: + self.centers_ = list(range(self.X.shape[0])) + self.labels_ = self.centers_ + self.effective_radius = 0 + self._mds_exec_time = 0 + return self + self.edges = np.argwhere(adj_mask).astype(np.int32) #TODO: changer en uint32 self.dist_mat = dist_mat self._clustering() diff --git a/radius_clustering/utils/mds3-util.h b/radius_clustering/utils/mds3-util.h index 1ef75e6..75d9e27 100644 --- a/radius_clustering/utils/mds3-util.h +++ b/radius_clustering/utils/mds3-util.h @@ -15,8 +15,16 @@ #include #include #include +#include + +#ifdef _WIN32 +#include +#elif defined(__APPLE__) || defined(__linux__) #include #include +#else +#error "Unsupported platform" +#endif #define WORD_LENGTH 100 #define TRUE 1 @@ -200,10 +208,27 @@ struct Result { }; static double get_utime() { - struct rusage utime; - getrusage(RUSAGE_SELF, &utime); - return (double) (utime.ru_utime.tv_sec - + (double) utime.ru_utime.tv_usec / 1000000); + #ifdef _WIN32 + FILETIME createTime; + FILETIME exitTime; + FILETIME kernelTime; + FILETIME userTime; + if (GetProcessTimes(GetCurrentProcess(), + &createTime, &exitTime, + &kernelTime, &userTime) != 0) { + ULARGE_INTEGER li = {{userTime.dwLowDateTime, userTime.dwHighDateTime}}; + return li.QuadPart * 1e-7; + } + return 0.0; + #elif defined(__APPLE__) || defined(__linux__) + struct rusage utime; + if (getrusage(RUSAGE_SELF, &utime) == 0) { + return (double)utime.ru_utime.tv_sec + (double)utime.ru_utime.tv_usec * 1e-6; + } + return 0.0; + #else + return (double)clock() / CLOCKS_PER_SEC; + #endif } static int cmp_branching_vertex_score(const void * a, const void *b){ From 7928d920717a4a95d11a1cfc118aec11a86dee8c Mon Sep 17 00:00:00 2001 From: Quentin Date: Fri, 20 Dec 2024 13:00:14 +0100 Subject: [PATCH 03/11] Adapting headers for crossplatform compile --- .github/workflows/build_wheels.yml | 1 + radius_clustering/utils/main-emos.c | 9 +++++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build_wheels.yml b/.github/workflows/build_wheels.yml index 86cedae..12c8d56 100644 --- a/.github/workflows/build_wheels.yml +++ b/.github/workflows/build_wheels.yml @@ -14,6 +14,7 @@ jobs: name: Build wheels on ${{ matrix.os }} runs-on: ${{ matrix.os }} strategy: + #fail-fast: false #commented until windows support is added matrix: # macos-13 is an intel runner, macos-14 is apple silicon os: [ubuntu-latest, windows-latest, macos-13, macos-14] diff --git a/radius_clustering/utils/main-emos.c b/radius_clustering/utils/main-emos.c index 6d25215..49e762b 100644 --- a/radius_clustering/utils/main-emos.c +++ b/radius_clustering/utils/main-emos.c @@ -12,11 +12,16 @@ Copyright (C) 2024, Haenn Quentin. #include #include #include -#include +#ifdef _WIN32 +#include +#elif defined(__APPLE__) || defined(__linux__) +#include +#include #include +#else +#error "Unsupported platform" #include #include -#include #include #include #include "mds3-util.h" From 332a3654837ee21ef0834a99720f6bc4342c9a8c Mon Sep 17 00:00:00 2001 From: Quentin Date: Fri, 20 Dec 2024 13:03:09 +0100 Subject: [PATCH 04/11] Fixing issue in main-emos.c and adding lias as author --- pyproject.toml | 2 ++ radius_clustering/utils/main-emos.c | 1 + 2 files changed, 3 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 480d149..e40697e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,6 +9,7 @@ description = "A Clustering under radius constraints algorithm using minimum dom readme = "README.md" authors = [ {name = "Quentin Haenn"}, + {name = "Lias Laboratory"} ] dependencies = [ @@ -37,6 +38,7 @@ classifiers=[ "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", "Programming Language :: Python :: Implementation :: CPython", ] keywords = ["Unsupervised learning","clustering", "minimum dominating sets","clustering under radius constraint"] diff --git a/radius_clustering/utils/main-emos.c b/radius_clustering/utils/main-emos.c index 49e762b..4c2b9ff 100644 --- a/radius_clustering/utils/main-emos.c +++ b/radius_clustering/utils/main-emos.c @@ -20,6 +20,7 @@ Copyright (C) 2024, Haenn Quentin. #include #else #error "Unsupported platform" +#endif #include #include #include From 933c93c71102939efa05f7cb1ca8ce69edbbd59e Mon Sep 17 00:00:00 2001 From: Quentin Date: Fri, 20 Dec 2024 13:17:21 +0100 Subject: [PATCH 05/11] reworked signal handler --- radius_clustering/utils/main-emos.c | 46 ++++++++++++++++++++++------- 1 file changed, 35 insertions(+), 11 deletions(-) diff --git a/radius_clustering/utils/main-emos.c b/radius_clustering/utils/main-emos.c index 4c2b9ff..f3c9312 100644 --- a/radius_clustering/utils/main-emos.c +++ b/radius_clustering/utils/main-emos.c @@ -10,21 +10,27 @@ Copyright (C) 2024, Haenn Quentin. #include #include #include -#include #include +#include +#include + #ifdef _WIN32 -#include + #include + #include + #include + #define SIGINT 2 + typedef void (*SignalHandlerFn)(int); #elif defined(__APPLE__) || defined(__linux__) -#include -#include -#include + #include + #include + #include + #include + #include #else -#error "Unsupported platform" + #error "Unsupported platform" #endif -#include -#include -#include -#include + + #include "mds3-util.h" #include "util_heap.h" @@ -1590,6 +1596,24 @@ void cleanup(){ } } +#ifdef _WIN32 +static BOOL WINAPI win32_handler(DWORD signal) { + if (signal == CTRL_C_EVENT) { + handler(SIGINT); + return TRUE; + } + return FALSE; +} + +static void setup_signal_handler(SignalHandlerFn handler_fn) { + SetConsoleCtrlHandler(win32_handler, TRUE); +} +#else +static void setup_signal_handler(void (*handler_fn)(int)) { + signal(SIGINT, handler_fn); +} +#endif + void handler(int sig) { cleanup(); exit(sig); @@ -1598,7 +1622,7 @@ void handler(int sig) { struct Result* emos_main(int* edges, int n, int nb_edge) { // Set the signal handler - signal(SIGINT, handler); + setup_signal_handler(SIGINT, handler); _read_graph_from_edge_list(edges, n, nb_edge); NB_NODE_O = NB_NODE; From f2923eab96d464125d136dbd67fcf651a0a9d7c4 Mon Sep 17 00:00:00 2001 From: Quentin Date: Fri, 20 Dec 2024 13:49:23 +0100 Subject: [PATCH 06/11] handling conflicts in prototypes --- radius_clustering/utils/mds3-util.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/radius_clustering/utils/mds3-util.h b/radius_clustering/utils/mds3-util.h index 75d9e27..6cc82ff 100644 --- a/radius_clustering/utils/mds3-util.h +++ b/radius_clustering/utils/mds3-util.h @@ -766,9 +766,9 @@ extern void search_domset(); extern int fast_search_initial_solution(); extern void solve_subproblems(); extern struct Result* emos_main(int* edges, int n, int nb_edge); -extern int* get_dominating_set(); -extern int get_set_size(); -extern double get_exec_time(); +extern int* get_dominating_set(struct Result* result); +extern int get_set_size(struct Result* result); +extern double get_exec_time(struct Result* result); extern void free_results(struct Result* result); // Declare global variables as extern From 6abe81528d94cf8f9a73656081c5f5e40cc681ae Mon Sep 17 00:00:00 2001 From: Quentin Date: Fri, 20 Dec 2024 13:53:52 +0100 Subject: [PATCH 07/11] fix error in handling signals capturing in main-emos.c --- radius_clustering/utils/main-emos.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/radius_clustering/utils/main-emos.c b/radius_clustering/utils/main-emos.c index f3c9312..c70c9e1 100644 --- a/radius_clustering/utils/main-emos.c +++ b/radius_clustering/utils/main-emos.c @@ -1622,7 +1622,7 @@ void handler(int sig) { struct Result* emos_main(int* edges, int n, int nb_edge) { // Set the signal handler - setup_signal_handler(SIGINT, handler); + setup_signal_handler(handler); _read_graph_from_edge_list(edges, n, nb_edge); NB_NODE_O = NB_NODE; From 3d22562fa4de2d07644f7ce460a6a0168cb75794 Mon Sep 17 00:00:00 2001 From: Quentin Date: Fri, 20 Dec 2024 14:27:23 +0100 Subject: [PATCH 08/11] fiw signing mixtures --- radius_clustering/radius_clustering.py | 2 +- radius_clustering/utils/emos.pyx | 4 +-- radius_clustering/utils/main-emos.c | 36 +++++++------------------- radius_clustering/utils/mds3-util.h | 4 +-- radius_clustering/utils/mds_core.cpp | 14 ---------- 5 files changed, 15 insertions(+), 45 deletions(-) diff --git a/radius_clustering/radius_clustering.py b/radius_clustering/radius_clustering.py index 60dfe36..5fbcc9f 100644 --- a/radius_clustering/radius_clustering.py +++ b/radius_clustering/radius_clustering.py @@ -102,7 +102,7 @@ def fit(self, X, y=None): self.effective_radius = 0 self._mds_exec_time = 0 return self - self.edges = np.argwhere(adj_mask).astype(np.int32) #TODO: changer en uint32 + self.edges = np.argwhere(adj_mask).astype(np.uint32) #TODO: changer en uint32 self.dist_mat = dist_mat self._clustering() diff --git a/radius_clustering/utils/emos.pyx b/radius_clustering/utils/emos.pyx index d6547b7..c6da2a5 100644 --- a/radius_clustering/utils/emos.pyx +++ b/radius_clustering/utils/emos.pyx @@ -17,7 +17,7 @@ cdef extern from "mds3-util.h": int set_size double exec_time - Result* emos_main(int* edges, int nb_edge, int n) + Result* emos_main(unsigned int* edges, int nb_edge, int n) void cleanup() @@ -26,7 +26,7 @@ cdef extern from "mds3-util.h": import numpy as np cimport numpy as np -def py_emos_main(np.ndarray[int, ndim=1] edges, int n, int nb_edge): +def py_emos_main(np.ndarray[unsigned int, ndim=1] edges, int n, int nb_edge): cdef Result* result = emos_main(&edges[0], n, nb_edge) dominating_set = [result.dominating_set[i] - 1 for i in range(result.set_size)] diff --git a/radius_clustering/utils/main-emos.c b/radius_clustering/utils/main-emos.c index c70c9e1..8015cb1 100644 --- a/radius_clustering/utils/main-emos.c +++ b/radius_clustering/utils/main-emos.c @@ -1385,7 +1385,7 @@ void solve_subproblems(){ static void print_final_solution(char *inst){ printf("--------------------------------\n"); printf("Solution: "); - for(int i=0;i #include -#include // Add this line #include #include #include #include -#include #include #include #include "random_manager.h" @@ -92,20 +90,8 @@ class Instance { std::unordered_set supportNodes; std::unordered_set leavesNodes; std::unordered_set unSelectedNodes; - int optimum; const bool supportAndLeafNodes = true; - /* - void constructAdjacencyList(const std::vector>& adjacency_matrix) { - for (int i = 0; i < numNodes; ++i) { - for (int j = 0; j < numNodes; ++j) { - if (adjacency_matrix[i][j] == 1) { - adjacencyList[i].push_back(j); - } - } - } - }*/ - void constructAdjacencyList(const std::vector& edge_list, int nb_edges) { for (int i = 0; i < 2 * nb_edges; i+=2) { int u = edge_list[i]; From b0dde777c4fd3ba00df0d946f2684630b4cc7980 Mon Sep 17 00:00:00 2001 From: Quentin Date: Fri, 20 Dec 2024 17:23:10 +0100 Subject: [PATCH 09/11] fiw signing mixtures --- radius_clustering/utils/mds3-util.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/radius_clustering/utils/mds3-util.h b/radius_clustering/utils/mds3-util.h index c109e13..d442dc5 100644 --- a/radius_clustering/utils/mds3-util.h +++ b/radius_clustering/utils/mds3-util.h @@ -255,7 +255,8 @@ static void parse_parmerters(int argc, char *argv[]) { } static void allcoate_memory_for_adjacency_list(int nb_node, int nb_edge,int offset) { - int i, block_size = 40960000, free_size = 0; + int i, block_size = 40960000; + unsigned int free_size = 0; Init_Adj_List = (int *) malloc((2 * nb_edge + nb_node) * sizeof(int)); if (Init_Adj_List == NULL ) { for (i = 1; i <= NB_NODE; i++) { From 242a546ffdb3bdcb1c85a1650045d35bf1941a67 Mon Sep 17 00:00:00 2001 From: Quentin Date: Fri, 20 Dec 2024 17:37:02 +0100 Subject: [PATCH 10/11] configuring cibuildwheel --- pyproject.toml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index e40697e..6d1a64d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -139,3 +139,8 @@ docstring-code-format = true # Set the line length limit used when formatting code snippets in # docstrings. docstring-code-line-length = "dynamic" + + +[tool.cibuildwheel] +# Skip building for PyPy, python 3.6/7/8 and 13t, and 32-bit platforms. +skip = ["pp*", "cp36-*", "cp37-*", "cp38-*", "*-win32", "*-manylinux_i686"] From 3fb487da804cbd2eb99773e20d97252669d0255a Mon Sep 17 00:00:00 2001 From: Quentin Date: Fri, 20 Dec 2024 18:29:27 +0100 Subject: [PATCH 11/11] Not building for musllinux --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 6d1a64d..a207a50 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -143,4 +143,4 @@ docstring-code-line-length = "dynamic" [tool.cibuildwheel] # Skip building for PyPy, python 3.6/7/8 and 13t, and 32-bit platforms. -skip = ["pp*", "cp36-*", "cp37-*", "cp38-*", "*-win32", "*-manylinux_i686"] +skip = ["pp*", "cp36-*", "cp37-*", "cp38-*", "*-win32", "*linux_i686", "*musllinux*"]