Compare commits
4 Commits
235367da01
...
main
Author | SHA1 | Date | |
---|---|---|---|
c2479bc7c7
|
|||
d073e26023
|
|||
38b27805ff
|
|||
417844f19b
|
3
.gitignore
vendored
3
.gitignore
vendored
@@ -35,4 +35,5 @@
|
||||
build_Release
|
||||
build_Debug
|
||||
build
|
||||
libtorch
|
||||
libtorch
|
||||
Testing
|
||||
|
@@ -1,7 +1,7 @@
|
||||
cmake_minimum_required(VERSION 3.15)
|
||||
project(SVMClassifier
|
||||
VERSION 1.0.0
|
||||
LANGUAGES CXX
|
||||
LANGUAGES C CXX
|
||||
DESCRIPTION "A C++ library for Support Vector Machine classification using PyTorch"
|
||||
HOMEPAGE_URL "https://gitea.rmontanana.es/rmontanana/SVMClassifier"
|
||||
)
|
||||
@@ -49,8 +49,21 @@ FetchContent_Declare(
|
||||
)
|
||||
FetchContent_MakeAvailable(Catch2)
|
||||
|
||||
# Add external libraries
|
||||
add_subdirectory(external)
|
||||
# Fetch libsvm
|
||||
FetchContent_Declare(
|
||||
libsvm
|
||||
GIT_REPOSITORY https://github.com/cjlin1/libsvm.git
|
||||
GIT_TAG v332
|
||||
)
|
||||
FetchContent_MakeAvailable(libsvm)
|
||||
|
||||
# Fetch liblinear
|
||||
FetchContent_Declare(
|
||||
liblinear
|
||||
GIT_REPOSITORY https://github.com/cjlin1/liblinear.git
|
||||
GIT_TAG v249
|
||||
)
|
||||
FetchContent_MakeAvailable(liblinear)
|
||||
|
||||
# Include directories
|
||||
include_directories(${CMAKE_SOURCE_DIR}/include)
|
||||
@@ -71,17 +84,47 @@ set(HEADERS
|
||||
include/svm_classifier/types.hpp
|
||||
)
|
||||
|
||||
# Create library
|
||||
add_library(svm_classifier STATIC ${SOURCES} ${HEADERS})
|
||||
# Add external sources directly to our library
|
||||
set(EXTERNAL_SOURCES)
|
||||
|
||||
# Link libraries - Updated to use object libraries instead of static libraries
|
||||
# Add libsvm sources
|
||||
if(EXISTS "${libsvm_SOURCE_DIR}/svm.cpp")
|
||||
list(APPEND EXTERNAL_SOURCES "${libsvm_SOURCE_DIR}/svm.cpp")
|
||||
endif()
|
||||
|
||||
# Add liblinear sources
|
||||
if(EXISTS "${liblinear_SOURCE_DIR}/linear.cpp")
|
||||
list(APPEND EXTERNAL_SOURCES "${liblinear_SOURCE_DIR}/linear.cpp")
|
||||
endif()
|
||||
|
||||
if(EXISTS "${liblinear_SOURCE_DIR}/newton.cpp")
|
||||
list(APPEND EXTERNAL_SOURCES "${liblinear_SOURCE_DIR}/newton.cpp")
|
||||
elseif(EXISTS "${liblinear_SOURCE_DIR}/tron.cpp")
|
||||
list(APPEND EXTERNAL_SOURCES "${liblinear_SOURCE_DIR}/tron.cpp")
|
||||
endif()
|
||||
|
||||
# Add BLAS sources
|
||||
if(EXISTS "${liblinear_SOURCE_DIR}/blas")
|
||||
file(GLOB BLAS_C_FILES "${liblinear_SOURCE_DIR}/blas/*.c")
|
||||
list(APPEND EXTERNAL_SOURCES ${BLAS_C_FILES})
|
||||
endif()
|
||||
|
||||
# Create library with all sources
|
||||
add_library(svm_classifier STATIC ${SOURCES} ${HEADERS} ${EXTERNAL_SOURCES})
|
||||
|
||||
# Set language properties for different file types
|
||||
foreach(source_file ${EXTERNAL_SOURCES})
|
||||
if(source_file MATCHES "\\.c$")
|
||||
set_source_files_properties(${source_file} PROPERTIES LANGUAGE C)
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
# Link libraries
|
||||
target_link_libraries(svm_classifier
|
||||
PUBLIC
|
||||
${TORCH_LIBRARIES}
|
||||
PRIVATE
|
||||
nlohmann_json::nlohmann_json
|
||||
$<TARGET_OBJECTS:libsvm_objects>
|
||||
$<TARGET_OBJECTS:liblinear_objects>
|
||||
)
|
||||
|
||||
# Set include directories
|
||||
@@ -91,27 +134,11 @@ target_include_directories(svm_classifier
|
||||
$<INSTALL_INTERFACE:include>
|
||||
PRIVATE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src
|
||||
${libsvm_SOURCE_DIR}
|
||||
${liblinear_SOURCE_DIR}
|
||||
${liblinear_SOURCE_DIR}/blas
|
||||
)
|
||||
|
||||
# Add include directories from external libraries using variables set by external/CMakeLists.txt
|
||||
if(LIBSVM_INCLUDE_DIR)
|
||||
target_include_directories(svm_classifier
|
||||
PRIVATE ${LIBSVM_INCLUDE_DIR}
|
||||
)
|
||||
endif()
|
||||
|
||||
if(LIBLINEAR_INCLUDE_DIR)
|
||||
target_include_directories(svm_classifier
|
||||
PRIVATE ${LIBLINEAR_INCLUDE_DIR}
|
||||
)
|
||||
endif()
|
||||
|
||||
if(LIBLINEAR_BLAS_INCLUDE_DIR)
|
||||
target_include_directories(svm_classifier
|
||||
PRIVATE ${LIBLINEAR_BLAS_INCLUDE_DIR}
|
||||
)
|
||||
endif()
|
||||
|
||||
# Compiler-specific options
|
||||
target_compile_features(svm_classifier PUBLIC cxx_std_17)
|
||||
|
||||
@@ -219,7 +246,7 @@ add_subdirectory(tests)
|
||||
# Add examples
|
||||
add_subdirectory(examples)
|
||||
|
||||
# Installation - Fixed version
|
||||
# Installation
|
||||
install(TARGETS svm_classifier
|
||||
EXPORT SVMClassifierTargets
|
||||
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
||||
@@ -279,4 +306,4 @@ else()
|
||||
set(CPACK_RPM_PACKAGE_REQUIRES "glibc, libstdc++, blas, lapack")
|
||||
endif()
|
||||
|
||||
include(CPack)
|
||||
include(CPack)
|
||||
|
2752
docs/Doxyfile.in
2752
docs/Doxyfile.in
File diff suppressed because it is too large
Load Diff
@@ -2,7 +2,9 @@
|
||||
|
||||
# Basic usage example
|
||||
add_executable(basic_usage basic_usage.cpp)
|
||||
target_link_libraries(basic_usage PRIVATE svm_classifier)
|
||||
target_link_libraries(basic_usage PRIVATE svm_classifier
|
||||
nlohmann_json::nlohmann_json
|
||||
)
|
||||
target_include_directories(basic_usage PRIVATE ${CMAKE_SOURCE_DIR}/include)
|
||||
target_compile_features(basic_usage PRIVATE cxx_std_17)
|
||||
|
||||
|
@@ -73,7 +73,7 @@ void basic_svm_example()
|
||||
std::cout << "Dataset created:" << std::endl;
|
||||
print_tensor_stats(X_train, "X_train");
|
||||
std::cout << "Unique classes in y_train: ";
|
||||
auto unique_classes = torch::unique(y_train);
|
||||
auto unique_classes = std::get<0>(at::_unique(y_train));
|
||||
for (int i = 0; i < unique_classes.size(0); ++i) {
|
||||
std::cout << unique_classes[i].item<int>() << " ";
|
||||
}
|
||||
|
180
external/CMakeLists.txt
vendored
180
external/CMakeLists.txt
vendored
@@ -1,178 +1,2 @@
|
||||
# External dependencies CMakeLists.txt - Fixed version with OBJECT libraries
|
||||
|
||||
include(FetchContent)
|
||||
|
||||
# Set policies for FetchContent
|
||||
if(POLICY CMP0135)
|
||||
cmake_policy(SET CMP0135 NEW)
|
||||
endif()
|
||||
|
||||
# Fetch libsvm
|
||||
FetchContent_Declare(
|
||||
libsvm
|
||||
GIT_REPOSITORY https://github.com/cjlin1/libsvm.git
|
||||
GIT_TAG v332
|
||||
)
|
||||
FetchContent_MakeAvailable(libsvm)
|
||||
|
||||
# Fetch liblinear
|
||||
FetchContent_Declare(
|
||||
liblinear
|
||||
GIT_REPOSITORY https://github.com/cjlin1/liblinear.git
|
||||
GIT_TAG v249
|
||||
)
|
||||
FetchContent_MakeAvailable(liblinear)
|
||||
|
||||
# Build libsvm as OBJECT library to avoid export issues
|
||||
if(EXISTS "${libsvm_SOURCE_DIR}/svm.cpp")
|
||||
set(LIBSVM_SOURCES "${libsvm_SOURCE_DIR}/svm.cpp")
|
||||
|
||||
add_library(libsvm_objects OBJECT ${LIBSVM_SOURCES})
|
||||
|
||||
# Set properties for the object library
|
||||
target_include_directories(libsvm_objects
|
||||
PRIVATE
|
||||
${libsvm_SOURCE_DIR}
|
||||
)
|
||||
|
||||
# Set C++ standard for libsvm
|
||||
target_compile_features(libsvm_objects PRIVATE cxx_std_17)
|
||||
|
||||
# Make the include directory available to parent scope
|
||||
set(LIBSVM_INCLUDE_DIR ${libsvm_SOURCE_DIR} PARENT_SCOPE)
|
||||
|
||||
message(STATUS "libsvm built successfully as object library")
|
||||
else()
|
||||
message(WARNING "libsvm source files not found")
|
||||
# Create empty object library
|
||||
add_library(libsvm_objects OBJECT)
|
||||
set(LIBSVM_INCLUDE_DIR "" PARENT_SCOPE)
|
||||
endif()
|
||||
|
||||
# Build liblinear as OBJECT library
|
||||
set(LIBLINEAR_SOURCES)
|
||||
|
||||
# Check for main liblinear source files
|
||||
if(EXISTS "${liblinear_SOURCE_DIR}/linear.cpp")
|
||||
list(APPEND LIBLINEAR_SOURCES "${liblinear_SOURCE_DIR}/linear.cpp")
|
||||
endif()
|
||||
|
||||
# Check for optimization files (tron.cpp, newton.cpp, etc.)
|
||||
if(EXISTS "${liblinear_SOURCE_DIR}/tron.cpp")
|
||||
list(APPEND LIBLINEAR_SOURCES "${liblinear_SOURCE_DIR}/tron.cpp")
|
||||
elseif(EXISTS "${liblinear_SOURCE_DIR}/newton.cpp")
|
||||
list(APPEND LIBLINEAR_SOURCES "${liblinear_SOURCE_DIR}/newton.cpp")
|
||||
endif()
|
||||
|
||||
# Check for BLAS files in blas directory - Fixed to ensure they're included
|
||||
if(EXISTS "${liblinear_SOURCE_DIR}/blas")
|
||||
# Explicitly add the required BLAS source files
|
||||
set(BLAS_FILES
|
||||
"${liblinear_SOURCE_DIR}/blas/daxpy.c"
|
||||
"${liblinear_SOURCE_DIR}/blas/ddot.c"
|
||||
"${liblinear_SOURCE_DIR}/blas/dnrm2.c"
|
||||
"${liblinear_SOURCE_DIR}/blas/dscal.c"
|
||||
)
|
||||
|
||||
# Check which BLAS files actually exist and add them
|
||||
foreach(blas_file ${BLAS_FILES})
|
||||
if(EXISTS ${blas_file})
|
||||
list(APPEND LIBLINEAR_SOURCES ${blas_file})
|
||||
message(STATUS "Adding BLAS file: ${blas_file}")
|
||||
endif()
|
||||
endforeach()
|
||||
else()
|
||||
message(WARNING "BLAS directory not found in liblinear source")
|
||||
endif()
|
||||
|
||||
# Create liblinear object library if we have source files
|
||||
if(LIBLINEAR_SOURCES)
|
||||
add_library(liblinear_objects OBJECT ${LIBLINEAR_SOURCES})
|
||||
|
||||
# Set properties for the object library
|
||||
target_include_directories(liblinear_objects
|
||||
PRIVATE
|
||||
${liblinear_SOURCE_DIR}
|
||||
)
|
||||
|
||||
# Add blas directory if it exists
|
||||
if(EXISTS "${liblinear_SOURCE_DIR}/blas")
|
||||
target_include_directories(liblinear_objects
|
||||
PRIVATE ${liblinear_SOURCE_DIR}/blas
|
||||
)
|
||||
endif()
|
||||
|
||||
# Set C++ standard for liblinear
|
||||
target_compile_features(liblinear_objects PRIVATE cxx_std_17)
|
||||
|
||||
# Compiler specific flags
|
||||
if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang")
|
||||
target_compile_options(liblinear_objects PRIVATE -w) # Suppress warnings
|
||||
endif()
|
||||
|
||||
# Make the include directories available to parent scope
|
||||
set(LIBLINEAR_INCLUDE_DIR ${liblinear_SOURCE_DIR} PARENT_SCOPE)
|
||||
if(EXISTS "${liblinear_SOURCE_DIR}/blas")
|
||||
set(LIBLINEAR_BLAS_INCLUDE_DIR ${liblinear_SOURCE_DIR}/blas PARENT_SCOPE)
|
||||
endif()
|
||||
|
||||
message(STATUS "liblinear built with sources: ${LIBLINEAR_SOURCES}")
|
||||
else()
|
||||
# Create minimal liblinear implementation as object library
|
||||
message(WARNING "liblinear source files not found, creating minimal implementation")
|
||||
|
||||
# Create a minimal linear.cpp file
|
||||
set(MINIMAL_LIBLINEAR_DIR "${CMAKE_CURRENT_BINARY_DIR}/minimal_liblinear")
|
||||
file(MAKE_DIRECTORY ${MINIMAL_LIBLINEAR_DIR})
|
||||
|
||||
# Create minimal header
|
||||
file(WRITE "${MINIMAL_LIBLINEAR_DIR}/linear.h"
|
||||
"#pragma once\n"
|
||||
"extern \"C\" {\n"
|
||||
"struct feature_node { int index; double value; };\n"
|
||||
"struct problem { int l, n; double *y; struct feature_node **x; double bias; };\n"
|
||||
"struct parameter { int solver_type; double eps, C; };\n"
|
||||
"struct model { struct parameter param; int nr_class, nr_feature; double *w; int *label; double bias; };\n"
|
||||
"struct model* train(const struct problem *prob, const struct parameter *param);\n"
|
||||
"double predict(const struct model *model_, const struct feature_node *x);\n"
|
||||
"void free_and_destroy_model(struct model **model_ptr_ptr);\n"
|
||||
"}\n"
|
||||
)
|
||||
|
||||
# Create minimal implementation
|
||||
file(WRITE "${MINIMAL_LIBLINEAR_DIR}/linear.cpp"
|
||||
"#include \"linear.h\"\n"
|
||||
"#include <cstdlib>\n"
|
||||
"#include <cstring>\n"
|
||||
"struct model* train(const struct problem *prob, const struct parameter *param) {\n"
|
||||
" auto model = (struct model*)malloc(sizeof(struct model));\n"
|
||||
" memset(model, 0, sizeof(struct model));\n"
|
||||
" model->nr_feature = prob->n;\n"
|
||||
" model->nr_class = 2;\n"
|
||||
" model->w = (double*)calloc(prob->n, sizeof(double));\n"
|
||||
" return model;\n"
|
||||
"}\n"
|
||||
"double predict(const struct model *model_, const struct feature_node *x) {\n"
|
||||
" return 1.0; // Dummy prediction\n"
|
||||
"}\n"
|
||||
"void free_and_destroy_model(struct model **model_ptr_ptr) {\n"
|
||||
" if (*model_ptr_ptr) {\n"
|
||||
" free((*model_ptr_ptr)->w);\n"
|
||||
" free(*model_ptr_ptr);\n"
|
||||
" *model_ptr_ptr = nullptr;\n"
|
||||
" }\n"
|
||||
"}\n"
|
||||
)
|
||||
|
||||
add_library(liblinear_objects OBJECT "${MINIMAL_LIBLINEAR_DIR}/linear.cpp")
|
||||
target_include_directories(liblinear_objects PRIVATE ${MINIMAL_LIBLINEAR_DIR})
|
||||
target_compile_features(liblinear_objects PRIVATE cxx_std_17)
|
||||
|
||||
set(LIBLINEAR_INCLUDE_DIR ${MINIMAL_LIBLINEAR_DIR} PARENT_SCOPE)
|
||||
endif()
|
||||
|
||||
# Print summary
|
||||
message(STATUS "External libraries configured:")
|
||||
message(STATUS " - libsvm: ${libsvm_SOURCE_DIR}")
|
||||
message(STATUS " - liblinear: ${liblinear_SOURCE_DIR}")
|
||||
message(STATUS " - Using OBJECT libraries to avoid export issues")
|
||||
# External dependencies are now handled directly in the main CMakeLists.txt
|
||||
# This file is kept for compatibility
|
||||
|
@@ -138,6 +138,7 @@ namespace svm_classifier {
|
||||
std::vector<double> linear_y_space_;
|
||||
|
||||
// Single sample storage (for prediction)
|
||||
// Thread-local storage for single sample conversions
|
||||
std::vector<svm_node> single_svm_nodes_;
|
||||
std::vector<feature_node> single_linear_nodes_;
|
||||
|
||||
|
@@ -92,7 +92,7 @@ namespace svm_classifier {
|
||||
linear_problem->n = n_features_;
|
||||
linear_problem->x = linear_x_space_.data();
|
||||
linear_problem->y = linear_y_space_.data();
|
||||
linear_problem->bias = -1; // No bias term by default
|
||||
linear_problem->bias = 1.0; // Add bias term with value 1.0
|
||||
|
||||
return linear_problem;
|
||||
}
|
||||
|
@@ -321,7 +321,8 @@ namespace svm_classifier {
|
||||
linear_params.p = 0.1;
|
||||
linear_params.nu = 0.5;
|
||||
linear_params.init_sol = nullptr;
|
||||
linear_params.regularize_bias = 0;
|
||||
linear_params.regularize_bias = 1;
|
||||
linear_params.w_recalc = false;
|
||||
|
||||
// Check parameters
|
||||
const char* error_msg = check_parameter(problem.get(), &linear_params);
|
||||
@@ -628,7 +629,8 @@ namespace svm_classifier {
|
||||
linear_params.p = 0.1;
|
||||
linear_params.nu = 0.5;
|
||||
linear_params.init_sol = nullptr;
|
||||
linear_params.regularize_bias = 0;
|
||||
linear_params.regularize_bias = 1;
|
||||
linear_params.w_recalc = false;
|
||||
|
||||
// Check parameters
|
||||
const char* error_msg = check_parameter(problem.get(), &linear_params);
|
||||
|
36
test_param_change.cpp
Normal file
36
test_param_change.cpp
Normal file
@@ -0,0 +1,36 @@
|
||||
#include <svm_classifier/svm_classifier.hpp>
|
||||
#include <torch/torch.h>
|
||||
#include <iostream>
|
||||
|
||||
using namespace svm_classifier;
|
||||
|
||||
int main() {
|
||||
try {
|
||||
std::cout << "Creating linear SVM..." << std::endl;
|
||||
SVMClassifier svm(KernelType::LINEAR, 1.0);
|
||||
|
||||
std::cout << "Generating data..." << std::endl;
|
||||
torch::manual_seed(42);
|
||||
auto X = torch::randn({50, 3});
|
||||
auto y = torch::randint(0, 2, {50});
|
||||
|
||||
std::cout << "Training SVM..." << std::endl;
|
||||
auto metrics = svm.fit(X, y);
|
||||
std::cout << "Training completed successfully!" << std::endl;
|
||||
std::cout << "Is fitted: " << svm.is_fitted() << std::endl;
|
||||
|
||||
std::cout << "\nChanging to RBF kernel..." << std::endl;
|
||||
nlohmann::json new_params = {{"kernel", "rbf"}};
|
||||
svm.set_parameters(new_params);
|
||||
std::cout << "Parameters changed successfully!" << std::endl;
|
||||
std::cout << "Is fitted after param change: " << svm.is_fitted() << std::endl;
|
||||
|
||||
std::cout << "\nAll operations completed successfully!" << std::endl;
|
||||
|
||||
} catch (const std::exception& e) {
|
||||
std::cerr << "Error: " << e.what() << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
@@ -24,6 +24,7 @@ target_link_libraries(svm_classifier_tests
|
||||
svm_classifier
|
||||
Catch2::Catch2WithMain
|
||||
nlohmann_json::nlohmann_json
|
||||
# No need to link external libraries as they're already in svm_classifier
|
||||
)
|
||||
|
||||
# Set include directories - Handle external libraries dynamically
|
||||
@@ -32,6 +33,20 @@ target_include_directories(svm_classifier_tests
|
||||
${CMAKE_SOURCE_DIR}/include
|
||||
)
|
||||
|
||||
# Add Catch2 include directories explicitly
|
||||
if(TARGET Catch2::Catch2)
|
||||
get_target_property(CATCH2_INCLUDE_DIRS Catch2::Catch2 INTERFACE_INCLUDE_DIRECTORIES)
|
||||
target_include_directories(svm_classifier_tests PRIVATE ${CATCH2_INCLUDE_DIRS})
|
||||
endif()
|
||||
|
||||
# Also add the source directory of Catch2 if available
|
||||
if(catch2_SOURCE_DIR)
|
||||
target_include_directories(svm_classifier_tests
|
||||
PRIVATE
|
||||
${catch2_SOURCE_DIR}/src
|
||||
)
|
||||
endif()
|
||||
|
||||
# Add libsvm include directory if available
|
||||
if(EXISTS "${CMAKE_CURRENT_BINARY_DIR}/../_deps/libsvm-src")
|
||||
target_include_directories(svm_classifier_tests
|
||||
|
@@ -3,7 +3,8 @@
|
||||
* @brief Unit tests for DataConverter class
|
||||
*/
|
||||
|
||||
#include <catch2/catch_all.hpp>
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
#include <catch2/catch_approx.hpp>
|
||||
#include <svm_classifier/data_converter.hpp>
|
||||
#include <torch/torch.h>
|
||||
|
||||
|
@@ -3,7 +3,8 @@
|
||||
* @brief Unit tests for KernelParameters class
|
||||
*/
|
||||
|
||||
#include <catch2/catch_all.hpp>
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
#include <catch2/catch_approx.hpp>
|
||||
#include <svm_classifier/kernel_parameters.hpp>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
|
@@ -7,7 +7,7 @@
|
||||
*/
|
||||
|
||||
#define CATCH_CONFIG_MAIN
|
||||
#include <catch2/catch_all.hpp>
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
#include <torch/torch.h>
|
||||
#include <iostream>
|
||||
|
||||
|
@@ -3,7 +3,8 @@
|
||||
* @brief Unit tests for multiclass strategy classes
|
||||
*/
|
||||
|
||||
#include <catch2/catch_all.hpp>
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
#include <catch2/catch_approx.hpp>
|
||||
#include <svm_classifier/multiclass_strategy.hpp>
|
||||
#include <svm_classifier/kernel_parameters.hpp>
|
||||
#include <svm_classifier/data_converter.hpp>
|
||||
|
@@ -3,7 +3,8 @@
|
||||
* @brief Integration tests for SVMClassifier class
|
||||
*/
|
||||
|
||||
#include <catch2/catch_all.hpp>
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
#include <catch2/catch_approx.hpp>
|
||||
#include <svm_classifier/svm_classifier.hpp>
|
||||
#include <torch/torch.h>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
Reference in New Issue
Block a user