Implement simple cut pool; avoid duplicate cuts
This commit is contained in:
@@ -114,7 +114,6 @@ int static add_subtour_cut(
|
|||||||
double rhs = 2.0 - 2.0 * type;
|
double rhs = 2.0 - 2.0 * type;
|
||||||
int newnz = cut_edges_count + type;
|
int newnz = cut_edges_count + type;
|
||||||
|
|
||||||
int rmatbeg = 0;
|
|
||||||
int *rmatind = 0;
|
int *rmatind = 0;
|
||||||
double *rmatval = 0;
|
double *rmatval = 0;
|
||||||
|
|
||||||
@@ -155,18 +154,27 @@ int static add_subtour_cut(
|
|||||||
abort_if(sum <= rhs - LP_EPSILON, "cannot add invalid cut");
|
abort_if(sum <= rhs - LP_EPSILON, "cannot add invalid cut");
|
||||||
}
|
}
|
||||||
|
|
||||||
rval = LP_add_rows(lp, 1, newnz, &rhs, &sense, &rmatbeg, rmatind,
|
struct Row *cut = 0;
|
||||||
rmatval);
|
cut = (struct Row *) malloc(sizeof(struct Row));
|
||||||
abort_if(rval, "LP_add_rows failed");
|
abort_if(!cut, "could not allocate cut");
|
||||||
|
|
||||||
|
cut->nz = newnz;
|
||||||
|
cut->sense = sense;
|
||||||
|
cut->rhs = rhs;
|
||||||
|
cut->rmatval = rmatval;
|
||||||
|
cut->rmatind = rmatind;
|
||||||
|
|
||||||
|
rval = LP_add_cut(lp, cut);
|
||||||
|
abort_if(rval, "LP_add_cut failed");
|
||||||
|
|
||||||
CLEANUP:
|
CLEANUP:
|
||||||
if (rmatval) free(rmatval);
|
|
||||||
if (rmatind) free(rmatind);
|
|
||||||
return rval;
|
return rval;
|
||||||
}
|
}
|
||||||
|
|
||||||
int find_exact_subtour_cuts(
|
int find_exact_subtour_cuts(
|
||||||
struct LP *lp, struct GTSP *data, int *total_added_cuts, double min_cut_violation)
|
struct LP *lp,
|
||||||
|
struct GTSP *data,
|
||||||
|
double min_cut_violation)
|
||||||
{
|
{
|
||||||
int rval = 0;
|
int rval = 0;
|
||||||
|
|
||||||
@@ -190,10 +198,11 @@ int find_exact_subtour_cuts(
|
|||||||
|
|
||||||
struct Graph digraph;
|
struct Graph digraph;
|
||||||
graph_init(&digraph);
|
graph_init(&digraph);
|
||||||
|
|
||||||
int digraph_edge_count = 4 * graph->edge_count + 2 * graph->node_count +
|
int digraph_edge_count = 4 * graph->edge_count + 2 * graph->node_count +
|
||||||
2 * data->cluster_count;
|
2 * data->cluster_count;
|
||||||
|
|
||||||
|
int original_cut_pool_size = lp->cut_pool_size;
|
||||||
|
|
||||||
capacities = (double *) malloc(digraph_edge_count * sizeof(double));
|
capacities = (double *) malloc(digraph_edge_count * sizeof(double));
|
||||||
abort_if(!capacities, "could not allocate capacities");
|
abort_if(!capacities, "could not allocate capacities");
|
||||||
|
|
||||||
@@ -202,29 +211,31 @@ int find_exact_subtour_cuts(
|
|||||||
|
|
||||||
// Constraints (2.1)
|
// Constraints (2.1)
|
||||||
rval = find_exact_subtour_cuts_cluster_to_cluster(lp, data, &digraph,
|
rval = find_exact_subtour_cuts_cluster_to_cluster(lp, data, &digraph,
|
||||||
capacities, &added_cuts_count, min_cut_violation);
|
capacities, min_cut_violation);
|
||||||
abort_if(rval, "find_exact_subtour_cuts_cluster_to_cluster failed");
|
abort_if(rval, "find_exact_subtour_cuts_cluster_to_cluster failed");
|
||||||
|
|
||||||
|
added_cuts_count = lp->cut_pool_size - original_cut_pool_size;
|
||||||
log_debug("Added %d cluster-to-cluster subtour cuts\n", added_cuts_count);
|
log_debug("Added %d cluster-to-cluster subtour cuts\n", added_cuts_count);
|
||||||
(*total_added_cuts) += added_cuts_count;
|
|
||||||
if (added_cuts_count > 0) goto CLEANUP;
|
if (added_cuts_count > 0) goto CLEANUP;
|
||||||
|
|
||||||
// Constraints (2.2)
|
// Constraints (2.2)
|
||||||
|
original_cut_pool_size = lp->cut_pool_size;
|
||||||
rval = find_exact_subtour_cuts_node_to_cluster(lp, data, x, &digraph,
|
rval = find_exact_subtour_cuts_node_to_cluster(lp, data, x, &digraph,
|
||||||
capacities, &added_cuts_count, min_cut_violation);
|
capacities, min_cut_violation);
|
||||||
abort_if(rval, "find_exact_subtour_cuts_node_to_cluster failed");
|
abort_if(rval, "find_exact_subtour_cuts_node_to_cluster failed");
|
||||||
|
|
||||||
|
added_cuts_count = lp->cut_pool_size - original_cut_pool_size;
|
||||||
log_debug("Added %d node-to-cluster subtour cuts\n", added_cuts_count);
|
log_debug("Added %d node-to-cluster subtour cuts\n", added_cuts_count);
|
||||||
(*total_added_cuts) += added_cuts_count;
|
|
||||||
if (added_cuts_count > 0) goto CLEANUP;
|
if (added_cuts_count > 0) goto CLEANUP;
|
||||||
|
|
||||||
// Constraints (2.3)
|
// Constraints (2.3)
|
||||||
|
original_cut_pool_size = lp->cut_pool_size;
|
||||||
rval = find_exact_subtour_cuts_node_to_node(lp, data, x, &digraph,
|
rval = find_exact_subtour_cuts_node_to_node(lp, data, x, &digraph,
|
||||||
capacities, &added_cuts_count, min_cut_violation);
|
capacities, min_cut_violation);
|
||||||
abort_if(rval, "find_exact_subtour_cuts_node_to_node failed");
|
abort_if(rval, "find_exact_subtour_cuts_node_to_node failed");
|
||||||
|
|
||||||
|
added_cuts_count = lp->cut_pool_size - original_cut_pool_size;
|
||||||
log_debug("Added %d node-to-node subtour cuts\n", added_cuts_count);
|
log_debug("Added %d node-to-node subtour cuts\n", added_cuts_count);
|
||||||
(*total_added_cuts) += added_cuts_count;
|
|
||||||
|
|
||||||
CLEANUP:
|
CLEANUP:
|
||||||
graph_free(&digraph);
|
graph_free(&digraph);
|
||||||
@@ -239,7 +250,6 @@ int find_exact_subtour_cuts_node_to_node(
|
|||||||
double *x,
|
double *x,
|
||||||
struct Graph *digraph,
|
struct Graph *digraph,
|
||||||
double *capacities,
|
double *capacities,
|
||||||
int *added_cuts_count,
|
|
||||||
double min_cut_violation)
|
double min_cut_violation)
|
||||||
{
|
{
|
||||||
int rval = 0;
|
int rval = 0;
|
||||||
@@ -309,8 +319,6 @@ int find_exact_subtour_cuts_node_to_node(
|
|||||||
rval = add_subtour_cut(lp, graph, from, to, cut_edges, cut_edges_count,
|
rval = add_subtour_cut(lp, graph, from, to, cut_edges, cut_edges_count,
|
||||||
2);
|
2);
|
||||||
abort_if(rval, "add_subtour_cut failed");
|
abort_if(rval, "add_subtour_cut failed");
|
||||||
|
|
||||||
(*added_cuts_count)++;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
CLEANUP:
|
CLEANUP:
|
||||||
@@ -325,7 +333,6 @@ int find_exact_subtour_cuts_node_to_cluster(
|
|||||||
double *x,
|
double *x,
|
||||||
struct Graph *digraph,
|
struct Graph *digraph,
|
||||||
double *capacities,
|
double *capacities,
|
||||||
int *added_cuts_count,
|
|
||||||
double min_cut_violation)
|
double min_cut_violation)
|
||||||
{
|
{
|
||||||
int rval = 0;
|
int rval = 0;
|
||||||
@@ -396,7 +403,6 @@ int find_exact_subtour_cuts_node_to_cluster(
|
|||||||
cut_edges_count, 1);
|
cut_edges_count, 1);
|
||||||
abort_if(rval, "add_subtour_cut failed");
|
abort_if(rval, "add_subtour_cut failed");
|
||||||
|
|
||||||
(*added_cuts_count)++;
|
|
||||||
cuts_count++;
|
cuts_count++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -412,7 +418,6 @@ int find_exact_subtour_cuts_cluster_to_cluster(
|
|||||||
struct GTSP *data,
|
struct GTSP *data,
|
||||||
struct Graph *digraph,
|
struct Graph *digraph,
|
||||||
double *capacities,
|
double *capacities,
|
||||||
int *added_cuts_count,
|
|
||||||
double min_cut_violation)
|
double min_cut_violation)
|
||||||
{
|
{
|
||||||
int rval = 0;
|
int rval = 0;
|
||||||
@@ -480,11 +485,8 @@ int find_exact_subtour_cuts_cluster_to_cluster(
|
|||||||
0);
|
0);
|
||||||
abort_if(rval, "add_subtour_cut failed");
|
abort_if(rval, "add_subtour_cut failed");
|
||||||
|
|
||||||
(*added_cuts_count)++;
|
|
||||||
cuts_count++;
|
cuts_count++;
|
||||||
}
|
}
|
||||||
|
|
||||||
// if(cuts_count > 0) break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
CLEANUP:
|
CLEANUP:
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ int find_exact_subtour_cuts_cluster_to_cluster(
|
|||||||
struct GTSP *data,
|
struct GTSP *data,
|
||||||
struct Graph *digraph,
|
struct Graph *digraph,
|
||||||
double *capacities,
|
double *capacities,
|
||||||
int *added_cuts_count,
|
|
||||||
double min_cut_violation);
|
double min_cut_violation);
|
||||||
|
|
||||||
int find_exact_subtour_cuts_node_to_cluster(
|
int find_exact_subtour_cuts_node_to_cluster(
|
||||||
@@ -21,7 +20,6 @@ int find_exact_subtour_cuts_node_to_cluster(
|
|||||||
double *x,
|
double *x,
|
||||||
struct Graph *digraph,
|
struct Graph *digraph,
|
||||||
double *capacities,
|
double *capacities,
|
||||||
int *added_cuts_count,
|
|
||||||
double min_cut_violation);
|
double min_cut_violation);
|
||||||
|
|
||||||
int find_exact_subtour_cuts_node_to_node(
|
int find_exact_subtour_cuts_node_to_node(
|
||||||
@@ -30,13 +28,11 @@ int find_exact_subtour_cuts_node_to_node(
|
|||||||
double *x,
|
double *x,
|
||||||
struct Graph *digraph,
|
struct Graph *digraph,
|
||||||
double *capacities,
|
double *capacities,
|
||||||
int *added_cuts_count,
|
|
||||||
double min_cut_violation);
|
double min_cut_violation);
|
||||||
|
|
||||||
int find_exact_subtour_cuts(
|
int find_exact_subtour_cuts(
|
||||||
struct LP *lp,
|
struct LP *lp,
|
||||||
struct GTSP *data,
|
struct GTSP *data,
|
||||||
int *total_added_cuts,
|
|
||||||
double min_cut_violation);
|
double min_cut_violation);
|
||||||
|
|
||||||
#endif //_PROJECT_GTSP_SUBTOUR_H_
|
#endif //_PROJECT_GTSP_SUBTOUR_H_
|
||||||
|
|||||||
15
src/gtsp.c
15
src/gtsp.c
@@ -188,7 +188,6 @@ int GTSP_add_cutting_planes(struct LP *lp, struct GTSP *data)
|
|||||||
int rval = 0;
|
int rval = 0;
|
||||||
|
|
||||||
int round = 0;
|
int round = 0;
|
||||||
int added_cuts_count = 0;
|
|
||||||
|
|
||||||
int violation_total = 3;
|
int violation_total = 3;
|
||||||
int violation_current = 0;
|
int violation_current = 0;
|
||||||
@@ -197,19 +196,17 @@ int GTSP_add_cutting_planes(struct LP *lp, struct GTSP *data)
|
|||||||
while (1)
|
while (1)
|
||||||
{
|
{
|
||||||
round++;
|
round++;
|
||||||
int added_cuts_this_round = 0;
|
|
||||||
|
|
||||||
log_debug("Finding subtour cuts, round %d, violation %.4lf...\n", round,
|
log_debug("Finding subtour cuts, round %d, violation %.4lf...\n", round,
|
||||||
violations[violation_current]);
|
violations[violation_current]);
|
||||||
|
|
||||||
rval = find_exact_subtour_cuts(lp, data, &added_cuts_this_round,
|
int original_cut_pool_size = lp->cut_pool_size;
|
||||||
violations[violation_current]);
|
rval = find_exact_subtour_cuts(lp, data, violations[violation_current]);
|
||||||
abort_if(rval, "find_exact_subtour_cuts failed");
|
abort_if(rval, "find_exact_subtour_cuts failed");
|
||||||
|
|
||||||
if (added_cuts_this_round == 0)
|
if (lp->cut_pool_size - original_cut_pool_size == 0)
|
||||||
{
|
{
|
||||||
++violation_current;
|
if (++violation_current < violation_total)
|
||||||
if (violation_current < violation_total)
|
|
||||||
{
|
{
|
||||||
log_debug("No cuts found. Decreasing minimum cut violation.\n");
|
log_debug("No cuts found. Decreasing minimum cut violation.\n");
|
||||||
continue;
|
continue;
|
||||||
@@ -234,7 +231,7 @@ int GTSP_add_cutting_planes(struct LP *lp, struct GTSP *data)
|
|||||||
abort_if(rval, "LP_get_obj_val failed");
|
abort_if(rval, "LP_get_obj_val failed");
|
||||||
|
|
||||||
log_debug(" obj val = %.4lf\n", obj_val);
|
log_debug(" obj val = %.4lf\n", obj_val);
|
||||||
log_debug(" time = %.2lf\n", time_after_lp-time_before_lp);
|
log_debug(" time = %.2lf\n", time_after_lp - time_before_lp);
|
||||||
|
|
||||||
if (time_after_lp - time_before_lp > 10.0)
|
if (time_after_lp - time_before_lp > 10.0)
|
||||||
{
|
{
|
||||||
@@ -248,8 +245,6 @@ int GTSP_add_cutting_planes(struct LP *lp, struct GTSP *data)
|
|||||||
abort_if(rval, "LP_optimize failed");
|
abort_if(rval, "LP_optimize failed");
|
||||||
|
|
||||||
if (is_infeasible) break;
|
if (is_infeasible) break;
|
||||||
|
|
||||||
added_cuts_count += added_cuts_this_round;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
CLEANUP:
|
CLEANUP:
|
||||||
|
|||||||
86
src/lp.c
86
src/lp.c
@@ -6,6 +6,7 @@
|
|||||||
#include "util.h"
|
#include "util.h"
|
||||||
|
|
||||||
#define LP_EPSILON 0.000001
|
#define LP_EPSILON 0.000001
|
||||||
|
#define MAX_CUT_POOL_SIZE 100000
|
||||||
|
|
||||||
int LP_open(struct LP *lp)
|
int LP_open(struct LP *lp)
|
||||||
{
|
{
|
||||||
@@ -13,11 +14,11 @@ int LP_open(struct LP *lp)
|
|||||||
|
|
||||||
lp->cplex_lp = (CPXLPptr) NULL;
|
lp->cplex_lp = (CPXLPptr) NULL;
|
||||||
lp->cplex_env = CPXopenCPLEX(&rval);
|
lp->cplex_env = CPXopenCPLEX(&rval);
|
||||||
if (rval)
|
abort_if(rval, "CPXopenCPLEX failed");
|
||||||
{
|
|
||||||
fprintf(stderr, "CPXopenCPLEX failed, return code %d\n", rval);
|
lp->cut_pool = (struct Row **) malloc(
|
||||||
goto CLEANUP;
|
MAX_CUT_POOL_SIZE * sizeof(struct Row *));
|
||||||
}
|
abort_if(!lp->cut_pool, "could not allocate cut_pool");
|
||||||
|
|
||||||
CLEANUP:
|
CLEANUP:
|
||||||
return rval;
|
return rval;
|
||||||
@@ -33,6 +34,7 @@ void LP_free(struct LP *lp)
|
|||||||
|
|
||||||
CPXcloseCPLEX(&lp->cplex_env);
|
CPXcloseCPLEX(&lp->cplex_env);
|
||||||
lp->cplex_env = 0;
|
lp->cplex_env = 0;
|
||||||
|
if (lp->cut_pool) free(lp->cut_pool);
|
||||||
}
|
}
|
||||||
|
|
||||||
int LP_create(struct LP *lp, const char *name)
|
int LP_create(struct LP *lp, const char *name)
|
||||||
@@ -158,9 +160,9 @@ int LP_remove_slacks(struct LP *lp, int first_row, double max_slack)
|
|||||||
int *should_remove = 0;
|
int *should_remove = 0;
|
||||||
|
|
||||||
int numrows = CPXgetnumrows(lp->cplex_env, lp->cplex_lp);
|
int numrows = CPXgetnumrows(lp->cplex_env, lp->cplex_lp);
|
||||||
if(numrows < 5000) return 0;
|
if (numrows < 5000) return 0;
|
||||||
|
|
||||||
should_remove = (int *) malloc((numrows+1) * sizeof(int));
|
should_remove = (int *) malloc((numrows + 1) * sizeof(int));
|
||||||
abort_if(!should_remove, "could not allocate should_remove");
|
abort_if(!should_remove, "could not allocate should_remove");
|
||||||
|
|
||||||
slacks = (double *) malloc(numrows * sizeof(double));
|
slacks = (double *) malloc(numrows * sizeof(double));
|
||||||
@@ -190,12 +192,12 @@ int LP_remove_slacks(struct LP *lp, int first_row, double max_slack)
|
|||||||
rval = CPXdelrows(lp->cplex_env, lp->cplex_lp, start - count,
|
rval = CPXdelrows(lp->cplex_env, lp->cplex_lp, start - count,
|
||||||
end - count);
|
end - count);
|
||||||
abort_if(rval, "CPXdelrows failed");
|
abort_if(rval, "CPXdelrows failed");
|
||||||
log_verbose(" %d %d (%d)\n", start, end, end-start+1);
|
log_verbose(" %d %d (%d)\n", start, end, end - start + 1);
|
||||||
|
|
||||||
count += end - start + 1;
|
count += end - start + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
start = i+1;
|
start = i + 1;
|
||||||
end = i;
|
end = i;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -252,3 +254,69 @@ int LP_write(struct LP *lp, const char *fname)
|
|||||||
CLEANUP:
|
CLEANUP:
|
||||||
return rval;
|
return rval;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#define return_if_neq(a, b) \
|
||||||
|
if((a)<(b)) return -1; \
|
||||||
|
if((a)>(b)) return 1;
|
||||||
|
|
||||||
|
#define return_if_neq_epsilon(a, b) \
|
||||||
|
if((a+LP_EPSILON)<(b)) return -1; \
|
||||||
|
if((a-LP_EPSILON)>(b)) return 1;
|
||||||
|
|
||||||
|
int compare_cuts(struct Row *cut1, struct Row *cut2)
|
||||||
|
{
|
||||||
|
return_if_neq(cut1->nz, cut2->nz);
|
||||||
|
|
||||||
|
for (int i = 0; i < cut1->nz; i++)
|
||||||
|
{
|
||||||
|
return_if_neq(cut1->rmatind[i], cut2->rmatind[i]);
|
||||||
|
return_if_neq_epsilon(cut1->rmatval[i], cut2->rmatval[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int LP_add_cut(struct LP *lp, struct Row *cut)
|
||||||
|
{
|
||||||
|
int rval = 0;
|
||||||
|
|
||||||
|
rval = LP_update_hash(cut);
|
||||||
|
abort_if(rval, "LP_update_hash failed");
|
||||||
|
|
||||||
|
for (int i = 0; i < lp->cut_pool_size; i++)
|
||||||
|
{
|
||||||
|
if (lp->cut_pool[i]->hash != cut->hash) continue;
|
||||||
|
if (!compare_cuts(lp->cut_pool[i], cut))
|
||||||
|
{
|
||||||
|
free(cut->rmatval);
|
||||||
|
free(cut->rmatind);
|
||||||
|
free(cut);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lp->cut_pool[lp->cut_pool_size++] = cut;
|
||||||
|
|
||||||
|
int rmatbeg = 0;
|
||||||
|
rval = LP_add_rows(lp, 1, cut->nz, &cut->rhs, &cut->sense, &rmatbeg,
|
||||||
|
cut->rmatind, cut->rmatval);
|
||||||
|
abort_if(rval, "LP_add_rows failed");
|
||||||
|
|
||||||
|
CLEANUP:
|
||||||
|
return rval;
|
||||||
|
}
|
||||||
|
|
||||||
|
int LP_update_hash(struct Row *cut)
|
||||||
|
{
|
||||||
|
unsigned long hash = 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < cut->nz; i++)
|
||||||
|
{
|
||||||
|
hash += cut->rmatind[i] * 65521;
|
||||||
|
hash %= 4294967291;
|
||||||
|
}
|
||||||
|
|
||||||
|
cut->hash = hash;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
19
src/lp.h
19
src/lp.h
@@ -9,6 +9,21 @@ struct LP
|
|||||||
{
|
{
|
||||||
CPXENVptr cplex_env;
|
CPXENVptr cplex_env;
|
||||||
CPXLPptr cplex_lp;
|
CPXLPptr cplex_lp;
|
||||||
|
|
||||||
|
int cut_pool_size;
|
||||||
|
struct Row **cut_pool;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Row
|
||||||
|
{
|
||||||
|
unsigned long hash;
|
||||||
|
// int cplex_row_index;
|
||||||
|
|
||||||
|
int nz;
|
||||||
|
char sense;
|
||||||
|
double rhs;
|
||||||
|
double *rmatval;
|
||||||
|
int *rmatind;
|
||||||
};
|
};
|
||||||
|
|
||||||
static const int MAX_NAME_LENGTH = 100;
|
static const int MAX_NAME_LENGTH = 100;
|
||||||
@@ -56,4 +71,8 @@ int LP_get_num_cols(struct LP *lp);
|
|||||||
|
|
||||||
int LP_remove_slacks(struct LP *lp, int start, double max_slack);
|
int LP_remove_slacks(struct LP *lp, int start, double max_slack);
|
||||||
|
|
||||||
|
int LP_update_hash(struct Row *cut);
|
||||||
|
|
||||||
|
int LP_add_cut(struct LP *lp, struct Row *cut);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
@@ -9,7 +9,7 @@
|
|||||||
#define LOG_LEVEL_DEBUG 40
|
#define LOG_LEVEL_DEBUG 40
|
||||||
#define LOG_LEVEL_VERBOSE 50
|
#define LOG_LEVEL_VERBOSE 50
|
||||||
|
|
||||||
#define LOG_LEVEL LOG_LEVEL_DEBUG
|
#define LOG_LEVEL LOG_LEVEL_INFO
|
||||||
|
|
||||||
#if LOG_LEVEL < LOG_LEVEL_VERBOSE
|
#if LOG_LEVEL < LOG_LEVEL_VERBOSE
|
||||||
#define log_verbose(...)
|
#define log_verbose(...)
|
||||||
|
|||||||
Reference in New Issue
Block a user