Skip to content

Commit c418b6e

Browse files
committed
Merge branch 'main' into memory-cleanup
# Conflicts: # radius_clustering/utils/main-emos.c
2 parents ba65745 + f776e83 commit c418b6e

File tree

8 files changed

+210
-60
lines changed

8 files changed

+210
-60
lines changed

.github/workflows/build_wheels.yml

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
name: Build and upload to PyPI
2+
3+
on:
4+
workflow_dispatch:
5+
pull_request:
6+
branches:
7+
- main
8+
push:
9+
branches:
10+
- main
11+
12+
jobs:
13+
build_wheels:
14+
name: Build wheels on ${{ matrix.os }}
15+
runs-on: ${{ matrix.os }}
16+
strategy:
17+
#fail-fast: false #commented until windows support is added
18+
matrix:
19+
# macos-13 is an intel runner, macos-14 is apple silicon
20+
os: [ubuntu-latest, windows-latest, macos-13, macos-14]
21+
22+
steps:
23+
- uses: actions/checkout@v4
24+
25+
- name: Build wheels
26+
uses: pypa/[email protected]
27+
28+
- uses: actions/upload-artifact@v4
29+
with:
30+
name: cibw-wheels-${{ matrix.os }}-${{ strategy.job-index }}
31+
path: ./wheelhouse/*.whl
32+
33+
build_sdist:
34+
name: Build source distribution
35+
runs-on: ubuntu-latest
36+
steps:
37+
- uses: actions/checkout@v4
38+
39+
- name: Build sdist
40+
run: pipx run build --sdist
41+
42+
- uses: actions/upload-artifact@v4
43+
with:
44+
name: cibw-sdist
45+
path: dist/*.tar.gz
46+
47+
upload_pypi:
48+
needs: [build_wheels, build_sdist]
49+
runs-on: ubuntu-latest
50+
environment: pypi
51+
permissions:
52+
id-token: write
53+
if: github.event_name == 'release' && github.event.action == 'published'
54+
# or, alternatively, upload to PyPI on every tag starting with 'v' (remove on: release above to use this)
55+
#if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')
56+
steps:
57+
- uses: actions/download-artifact@v4
58+
with:
59+
# unpacks all CIBW artifacts into dist/
60+
pattern: cibw-*
61+
path: dist
62+
merge-multiple: true
63+
64+
- name: Generate artifact attestations
65+
uses: actions/[email protected]
66+
with:
67+
subject-path: "dist/*"
68+
69+
- uses: pypa/gh-action-pypi-publish@release/v1
70+
#with:
71+
# To test: repository-url: https://test.pypi.org/legacy/

CITATION.cff

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# This CITATION.cff file was generated with cffinit.
2+
# Visit https://bit.ly/cffinit to generate yours today!
3+
4+
cff-version: 1.2.0
5+
title: Radius Clustering
6+
message: >-
7+
If you use this software, please cite it using the
8+
metadata from this file.
9+
type: software
10+
authors:
11+
- given-names: Quentin
12+
family-names: Haenn
13+
14+
affiliation: LIAS Lab
15+
orcid: 'https://orcid.org/0009-0009-1663-0107'
16+
- given-names: Brice
17+
family-names: Chardin
18+
19+
affiliation: LIAS Lab
20+
orcid: 'https://orcid.org/0000-0002-9298-9447'
21+
- given-names: Mickael
22+
family-names: Baron
23+
24+
affiliation: LIAS Lab
25+
orcid: 'https://orcid.org/0000-0002-3356-0835'
26+
- name: LIAS Laboratory
27+
address: 1 Avenue Clément Ader
28+
city: Chasseneuil du Poitou
29+
post-code: '86360'
30+
website: 'https://www.lias-lab.fr'
31+
identifiers:
32+
- type: swh
33+
value: 'swh:1:rev:66f8d295cc5fbc80f356d11be46571bfbb190609'
34+
license: GPL-3.0

pyproject.toml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ description = "A Clustering under radius constraints algorithm using minimum dom
99
readme = "README.md"
1010
authors = [
1111
{name = "Quentin Haenn"},
12+
{name = "Lias Laboratory"}
1213
]
1314

1415
dependencies = [
@@ -37,6 +38,7 @@ classifiers=[
3738
"Programming Language :: Python :: 3.10",
3839
"Programming Language :: Python :: 3.11",
3940
"Programming Language :: Python :: 3.12",
41+
"Programming Language :: Python :: 3.13",
4042
"Programming Language :: Python :: Implementation :: CPython",
4143
]
4244
keywords = ["Unsupervised learning","clustering", "minimum dominating sets","clustering under radius constraint"]
@@ -137,3 +139,8 @@ docstring-code-format = true
137139
# Set the line length limit used when formatting code snippets in
138140
# docstrings.
139141
docstring-code-line-length = "dynamic"
142+
143+
144+
[tool.cibuildwheel]
145+
# Skip building for PyPy, python 3.6/7/8 and 13t, and 32-bit platforms.
146+
skip = ["pp*", "cp36-*", "cp37-*", "cp38-*", "*-win32", "*linux_i686", "*musllinux*"]

radius_clustering/radius_clustering.py

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111
import os
1212
import numpy as np
13-
import scipy.spatial as sp_spatial
13+
from sklearn.metrics import pairwise_distances
1414
from sklearn.base import BaseEstimator, ClusterMixin
1515
from sklearn.utils.validation import check_array
1616

@@ -38,7 +38,7 @@ class RadiusClustering(BaseEstimator, ClusterMixin):
3838
-----------
3939
X : array-like, shape (n_samples, n_features)
4040
The input data.
41-
centers : list
41+
centers\_ : list
4242
The indices of the cluster centers.
4343
labels\_ : array-like, shape (n_samples,)
4444
The cluster labels for each point in the input data.
@@ -50,6 +50,9 @@ def __init__(self, manner="approx", threshold=0.5):
5050
self.manner = manner
5151
self.threshold = threshold
5252

53+
def _check_symmetric(self, a, tol=1e-8):
54+
return np.allclose(a, a.T, atol=tol)
55+
5356
def fit(self, X, y=None):
5457
"""
5558
Fit the MDS clustering model to the input data.
@@ -87,10 +90,19 @@ def fit(self, X, y=None):
8790
self.X = check_array(X)
8891

8992
# Create dist and adj matrices
90-
dist_mat = sp_spatial.distance_matrix(self.X, self.X)
93+
if not self._check_symmetric(self.X):
94+
dist_mat = pairwise_distances(self.X, metric="euclidean")
95+
else:
96+
dist_mat = self.X
9197
adj_mask = np.triu((dist_mat <= self.threshold), k=1)
9298
self.nb_edges = np.sum(adj_mask)
93-
self.edges = np.argwhere(adj_mask).astype(np.int32)
99+
if self.nb_edges == 0:
100+
self.centers_ = list(range(self.X.shape[0]))
101+
self.labels_ = self.centers_
102+
self.effective_radius = 0
103+
self._mds_exec_time = 0
104+
return self
105+
self.edges = np.argwhere(adj_mask).astype(np.uint32) #TODO: changer en uint32
94106
self.dist_mat = dist_mat
95107

96108
self._clustering()

radius_clustering/utils/emos.pyx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ cdef extern from "mds3-util.h":
1717
int set_size
1818
double exec_time
1919

20-
Result* emos_main(int* edges, int nb_edge, int n)
20+
Result* emos_main(unsigned int* edges, int nb_edge, int n)
2121

2222
void cleanup()
2323

@@ -26,7 +26,7 @@ cdef extern from "mds3-util.h":
2626
import numpy as np
2727
cimport numpy as np
2828

29-
def py_emos_main(np.ndarray[int, ndim=1] edges, int n, int nb_edge):
29+
def py_emos_main(np.ndarray[unsigned int, ndim=1] edges, int n, int nb_edge):
3030
cdef Result* result = emos_main(&edges[0], n, nb_edge)
3131

3232
dominating_set = [result.dominating_set[i] - 1 for i in range(result.set_size)]

radius_clustering/utils/main-emos.c

Lines changed: 44 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,27 @@ Copyright (C) 2024, Haenn Quentin.
1010
#include <stdio.h>
1111
#include <stdlib.h>
1212
#include <string.h>
13-
#include <signal.h>
1413
#include <time.h>
15-
#include <sys/times.h>
16-
#include <sys/types.h>
17-
#include <limits.h>
18-
#include <unistd.h>
19-
#include <sys/resource.h>
20-
#include <math.h>
2114
#include <assert.h>
15+
#include <math.h>
16+
17+
#ifdef _WIN32
18+
#include <windows.h>
19+
#include <process.h>
20+
#include <direct.h>
21+
#define SIGINT 2
22+
typedef void (*SignalHandlerFn)(int);
23+
#elif defined(__APPLE__) || defined(__linux__)
24+
#include <sys/time.h>
25+
#include <sys/resource.h>
26+
#include <sys/types.h>
27+
#include <unistd.h>
28+
#include <signal.h>
29+
#else
30+
#error "Unsupported platform"
31+
#endif
32+
33+
2234
#include "mds3-util.h"
2335
#include "util_heap.h"
2436

@@ -1462,7 +1474,7 @@ void check_consistance(){
14621474
assert(!domed(CFG[i]));
14631475
}
14641476
int level=-1;
1465-
for(int idx=0;idx<USED(BRA_STK);idx++){
1477+
for(size_t idx=0;idx<USED(BRA_STK);idx++){
14661478
if(ITEM(BRA_STK,idx)==NONE){
14671479
level++;
14681480
}else if(idx<=BRAIDX[level])
@@ -1487,7 +1499,7 @@ void check_consistance(){
14871499

14881500

14891501
void cleanup(){
1490-
1502+
14911503
// Free all allocated memory
14921504
if (CFG != NULL) {
14931505
free(CFG);
@@ -1554,15 +1566,35 @@ void cleanup(){
15541566
instance[0] = '\0';
15551567
}
15561568

1569+
15571570
void handler(int sig) {
15581571
cleanup();
15591572
exit(sig);
15601573
}
15611574

1562-
struct Result* emos_main(int* edges, int n, int nb_edge) {
1575+
#ifdef _WIN32
1576+
static BOOL WINAPI win32_handler(DWORD signal) {
1577+
if (signal == CTRL_C_EVENT) {
1578+
handler(SIGINT);
1579+
return TRUE;
1580+
}
1581+
return FALSE;
1582+
}
1583+
1584+
static void setup_signal_handler(SignalHandlerFn handler_fn) {
1585+
SetConsoleCtrlHandler(win32_handler, TRUE);
1586+
}
1587+
#else
1588+
static void setup_signal_handler(void (*handler_fn)(int)) {
1589+
signal(SIGINT, handler_fn);
1590+
}
1591+
#endif
1592+
1593+
1594+
struct Result* emos_main(unsigned int* edges, int n, int nb_edge) {
15631595

15641596
// Set the signal handler
1565-
signal(SIGINT, handler);
1597+
setup_signal_handler(handler);
15661598

15671599
_read_graph_from_edge_list(edges, n, nb_edge);
15681600
NB_NODE_O = NB_NODE;
@@ -1584,7 +1616,7 @@ struct Result* emos_main(int* edges, int n, int nb_edge) {
15841616

15851617
// Get the results
15861618
int* dominating_set = (int*)malloc(USED(VEC_SOLUTION) * sizeof(int));
1587-
for (int i= 0; i<USED(VEC_SOLUTION); i++) {
1619+
for (size_t i= 0; i<USED(VEC_SOLUTION); i++) {
15881620
dominating_set[i] = ITEM(VEC_SOLUTION, i);
15891621
}
15901622

@@ -1615,22 +1647,4 @@ void free_results(struct Result* result) {
16151647
free(result);
16161648
}
16171649
}
1618-
1619-
/** int main(int argc, char *argv[]) {
1620-
1621-
print_compile_options();
1622-
parse_parmerters(argc,argv);
1623-
if(read_instance(argv[1])) {
1624-
initialize();
1625-
#ifndef NOR
1626-
reduce_graph();
1627-
#endif
1628-
partition_oneproblem();
1629-
solve_subproblems();
1630-
check_final_solution();
1631-
print_final_solution(getInstanceName(argv[1]));
1632-
printf("### %s pruning rate %0.2lf total %llu pruned %llu\n",getInstanceName(argv[1]), (total_branches-pruned_branches)/((double)total_branches),total_branches,total_branches-pruned_branches);
1633-
}
1634-
return 0;
1635-
} */
16361650

0 commit comments

Comments
 (0)