Skip to content

Commit

Permalink
Merge pull request #6 from lias-laboratory/ci-wheel
Browse files Browse the repository at this point in the history
Added CI Workflow for publishing automatically
  • Loading branch information
quentinhaenn authored Dec 20, 2024
2 parents a6816e5 + 3fb487d commit f776e83
Show file tree
Hide file tree
Showing 7 changed files with 177 additions and 61 deletions.
71 changes: 71 additions & 0 deletions .github/workflows/build_wheels.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
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:
#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]

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/
9 changes: 8 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@ 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 = [
{name = "Quentin Haenn"},
{name = "Lias Laboratory"}
]

dependencies = [
Expand Down Expand Up @@ -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"]
Expand Down Expand Up @@ -137,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", "*linux_i686", "*musllinux*"]
20 changes: 16 additions & 4 deletions radius_clustering/radius_clustering.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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.
Expand All @@ -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.
Expand Down Expand Up @@ -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.uint32) #TODO: changer en uint32
self.dist_mat = dist_mat

self._clustering()
Expand Down
4 changes: 2 additions & 2 deletions radius_clustering/utils/emos.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand All @@ -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)]
Expand Down
74 changes: 44 additions & 30 deletions radius_clustering/utils/main-emos.c
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,27 @@ Copyright (C) 2024, Haenn Quentin.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <time.h>
#include <sys/times.h>
#include <sys/types.h>
#include <limits.h>
#include <unistd.h>
#include <sys/resource.h>
#include <math.h>
#include <assert.h>
#include <math.h>

#ifdef _WIN32
#include <windows.h>
#include <process.h>
#include <direct.h>
#define SIGINT 2
typedef void (*SignalHandlerFn)(int);
#elif defined(__APPLE__) || defined(__linux__)
#include <sys/time.h>
#include <sys/resource.h>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>
#else
#error "Unsupported platform"
#endif


#include "mds3-util.h"
#include "util_heap.h"

Expand Down Expand Up @@ -1373,7 +1385,7 @@ void solve_subproblems(){
static void print_final_solution(char *inst){
printf("--------------------------------\n");
printf("Solution: ");
for(int i=0;i<USED(VEC_SOLUTION);i++){
for(size_t i=0;i<USED(VEC_SOLUTION);i++){
printf("%d ",ITEM(VEC_SOLUTION,i));
}
printf("\n");
Expand Down Expand Up @@ -1476,7 +1488,7 @@ void check_consistance(){
assert(!domed(CFG[i]));
}
int level=-1;
for(int idx=0;idx<USED(BRA_STK);idx++){
for(size_t idx=0;idx<USED(BRA_STK);idx++){
if(ITEM(BRA_STK,idx)==NONE){
level++;
}else if(idx<=BRAIDX[level])
Expand Down Expand Up @@ -1584,15 +1596,35 @@ void cleanup(){
}
}


void handler(int sig) {
cleanup();
exit(sig);
}

struct Result* emos_main(int* edges, int n, int nb_edge) {
#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


struct Result* emos_main(unsigned int* edges, int n, int nb_edge) {

// Set the signal handler
signal(SIGINT, handler);
setup_signal_handler(handler);

_read_graph_from_edge_list(edges, n, nb_edge);
NB_NODE_O = NB_NODE;
Expand All @@ -1614,7 +1646,7 @@ struct Result* emos_main(int* edges, int n, int nb_edge) {

// Get the results
int* dominating_set = (int*)malloc(USED(VEC_SOLUTION) * sizeof(int));
for (int i= 0; i<USED(VEC_SOLUTION); i++) {
for (size_t i= 0; i<USED(VEC_SOLUTION); i++) {
dominating_set[i] = ITEM(VEC_SOLUTION, i);
}

Expand Down Expand Up @@ -1645,22 +1677,4 @@ void free_results(struct Result* result) {
free(result);
}
}

/** int main(int argc, char *argv[]) {
print_compile_options();
parse_parmerters(argc,argv);
if(read_instance(argv[1])) {
initialize();
#ifndef NOR
reduce_graph();
#endif
partition_oneproblem();
solve_subproblems();
check_final_solution();
print_final_solution(getInstanceName(argv[1]));
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);
}
return 0;
} */

46 changes: 36 additions & 10 deletions radius_clustering/utils/mds3-util.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,16 @@
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <time.h>

#ifdef _WIN32
#include <windows.h>
#elif defined(__APPLE__) || defined(__linux__)
#include <sys/time.h>
#include <sys/resource.h>
#else
#error "Unsupported platform"
#endif

#define WORD_LENGTH 100
#define TRUE 1
Expand Down Expand Up @@ -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){
Expand All @@ -230,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++) {
Expand Down Expand Up @@ -317,7 +343,7 @@ static int _read_graph_from_adjacency_matrix(int** adj_matrix, int num_nodes) {
return TRUE;
}

static int _read_graph_from_edge_list(int* edges, int n, int nb_edges) {
static int _read_graph_from_edge_list(unsigned int* edges, int n, int nb_edges) {
int i, j, l_node, r_node, nb_edge = 0, max_node = n, offset = 0;
int node = 1;

Expand Down Expand Up @@ -740,10 +766,10 @@ extern int select_branching_node();
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 struct Result* emos_main(unsigned int* edges, int n, int nb_edge);
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
Expand Down
Loading

0 comments on commit f776e83

Please sign in to comment.